【前端面试小册】JS-第9节:try-catch 进阶与异常处理
一、错误捕获的常见误区:同步 vs 异步
1.1 核心问题
在日常开发中,try...catch 是常用的错误处理方式。但很多开发者容易忽略一个关键点:try...catch 只能捕获同步代码中的错误,无法捕获异步操作中的错误。
类比理解:就像你站在门口等快递,如果快递员在门口直接给你(同步),你能立即收到。但如果快递员把包裹放在快递柜,等你去取的时候(异步),你已经不在门口了,自然收不到。
1.2 面试题一:setTimeout 中的错误
题目:以下代码有错吗?能捕获到错误吗?
try {
setTimeout(() => {
throw new Error('err');
}, 200);
} catch (err) {
console.log(err);
}
答案:❌ 无法捕获错误
原因分析:
graph TD
A[try 块开始] --> B[注册 setTimeout]
B --> C[try 块结束]
C --> D[catch 块执行完毕]
D --> E[200ms 后]
E --> F[setTimeout 回调执行]
F --> G[抛出错误]
G --> H[错误无法被捕获]
关键理解:
setTimeout是异步操作,回调函数在事件循环的下一个轮次才执行- 当回调函数执行时,
try...catch已经执行完毕 - 异步回调中的错误会直接抛到全局,导致未捕获的异常
正确做法:
// ✅ 方式一:在异步回调内部使用 try-catch
new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw new Error('err');
} catch (err) {
reject(err); // 将错误传递给 Promise
}
}, 200);
})
.then(() => {
// 正常逻辑
})
.catch((err) => {
console.log(err); // ✅ 捕获错误
});
// ✅ 方式二:使用 Promise 的 reject
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('err')); // 直接 reject
}, 200);
})
.catch((err) => {
console.log(err); // ✅ 捕获错误
});
1.3 面试题二:Promise 链中的错误
题目:如下代码有错吗?能捕获到错误吗?
try {
Promise.resolve().then(() => {
throw new Error('err');
});
} catch (err) {
console.log(err);
}
答案:❌ 无法捕获错误
原因分析:
Promise.then()是异步操作,回调函数在微任务队列中执行try...catch只能捕获同步错误和await后的异步错误- Promise 链中的错误必须使用
.catch()或await + try/catch捕获
正确做法:
// ✅ 方式一:使用 .catch()
Promise.resolve()
.then(() => {
throw new Error('err');
})
.catch((err) => {
console.log(err); // ✅ 这里会捕捉到错误
});
// ✅ 方式二:使用 async/await + try/catch
async function handleError() {
try {
await Promise.resolve().then(() => {
throw new Error('err');
});
} catch (err) {
console.log(err); // ✅ 这里会捕捉到错误
}
}
handleError();
执行流程对比:
graph TD
A[Promise.then 注册回调] --> B[try 块结束]
B --> C[微任务队列执行]
C --> D[回调函数执行]
D --> E[抛出错误]
E --> F{使用 catch}
F -->|是| G[错误被捕获]
F -->|否| H[错误抛到全局]
二、异常捕获的扩展应用
2.1 捕获未处理的 Promise 错误
对于未捕获的 Promise 错误,可以通过全局监听 unhandledrejection 事件来捕获:
// ✅ 浏览器环境
window.addEventListener('unhandledrejection', function (event) {
console.error('未处理的 Promise 错误:', event.reason);
// 可以在这里发送错误报告到监控平台
event.preventDefault(); // 阻止默认的错误输出
});
// ✅ Node.js 环境
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise rejection:', reason);
});
使用场景:
- 全局错误监控和上报
- 开发环境下的错误提示
- 生产环境的错误日志收集
2.2 捕获其他未捕获的异常
使用 window.onerror 捕获未被处理的同步或异步错误:
window.onerror = function (message, source, lineno, colno, error) {
console.error('捕获到错误:', {
message, // 错误信息
source, // 发生错误的文件
lineno, // 行号
colno, // 列号
error // Error 对象
});
// 返回 true 可以阻止默认的错误处理
return true;
};
注意:window.onerror 只能捕获:
- ✅ 同步错误
- ✅ 部分异步错误(如 setTimeout 中的错误)
- ❌ 无法捕获 Promise rejection(需要使用
unhandledrejection)
2.3 资源加载错误处理
通过监听 error 事件捕获资源加载失败,并在失败时进行重新加载:
// ✅ 捕获资源加载错误
document.addEventListener('error', function (e) {
if (e.target.tagName === 'IMG') {
console.error('图片加载失败:', e.target.src);
// 重试加载图片
e.target.src = e.target.src;
} else if (e.target.tagName === 'SCRIPT') {
console.error('脚本加载失败:', e.target.src);
// 可以切换到备用 CDN
} else if (e.target.tagName === 'LINK') {
console.error('样式表加载失败:', e.target.href);
}
}, true); // 使用捕获阶段
常见资源类型:
IMG:图片资源SCRIPT:JavaScript 文件LINK:CSS 样式表VIDEO/AUDIO:音视频资源IFRAME:嵌入的外部页面
2.4 CDN 资源加载失败处理
针对 CDN 资源,可以在资源加载失败时重试或切换到备用 CDN:
function loadImage(src, fallbackSrc) {
const img = new Image();
img.onload = function() {
console.log('图片加载成功');
document.body.appendChild(img);
};
img.onerror = function() {
console.error(`图片加载失败: ${src},使用备用 CDN: ${fallbackSrc}`);
img.src = fallbackSrc; // 使用备用图片地址
};
img.src = src;
}
// 使用示例:图片加载时使用备用 CDN
loadImage(
'https://primary-cdn.com/image.jpg',
'https://backup-cdn.com/image.jpg'
);
进阶:实现自动重试机制:
function loadImageWithRetry(src, maxRetries = 3) {
return new Promise((resolve, reject) => {
let retries = 0;
function attemptLoad() {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => {
retries++;
if (retries < maxRetries) {
console.log(`重试加载图片 (${retries}/${maxRetries}):`, src);
attemptLoad();
} else {
reject(new Error(`图片加载失败,已重试 ${maxRetries} 次`));
}
};
img.src = src;
}
attemptLoad();
});
}
// 使用示例
loadImageWithRetry('https://example.com/image.jpg', 3)
.then(img => document.body.appendChild(img))
.catch(err => console.error(err));
三、try-catch 的最佳实践
3.1 避免滥用 try-catch
原则:不要使用 try-catch 控制正常流程,应该仅用于不可预见的异常。
// ❌ 不好的做法:用 try-catch 控制流程
function getUser(id) {
try {
const user = users.find(u => u.id === id);
if (!user) {
throw new Error('用户不存在');
}
return user;
} catch (error) {
return null;
}
}
// ✅ 好的做法:正常流程用 if-else
function getUser(id) {
const user = users.find(u => u.id === id);
if (!user) {
return null; // 或返回默认值
}
return user;
}
3.2 局部捕获错误
只在需要捕获的代码块中使用 try-catch,而非整个函数,减少错误排查难度。
// ❌ 不好的做法:整个函数包裹
function processData(data) {
try {
const parsed = JSON.parse(data);
const processed = process(parsed);
const formatted = format(processed);
return formatted;
} catch (error) {
console.error('处理失败');
return null;
}
}
// ✅ 好的做法:局部捕获
function processData(data) {
let parsed;
try {
parsed = JSON.parse(data);
} catch (error) {
console.error('JSON 解析失败:', error);
return null;
}
// 其他处理逻辑不需要 try-catch
const processed = process(parsed);
const formatted = format(processed);
return formatted;
}
3.3 使用 finally 进行清理操作
finally 块无论是否有错误都会执行,用于资源清理等操作。
function readFile(filename) {
let fileHandle;
try {
fileHandle = openFile(filename);
const content = readContent(fileHandle);
return content;
} catch (error) {
console.error('读取文件失败:', error);
throw error; // 重新抛出错误
} finally {
// ✅ 无论成功或失败,都要关闭文件
if (fileHandle) {
closeFile(fileHandle);
}
}
}
常见清理场景:
- 关闭文件句柄
- 释放数据库连接
- 清理定时器
- 重置状态
3.4 提供有意义的错误信息
// ❌ 不好的做法:错误信息不明确
try {
const data = JSON.parse(jsonString);
} catch (error) {
console.error('错误');
}
// ✅ 好的做法:提供详细的错误信息
try {
const data = JSON.parse(jsonString);
} catch (error) {
console.error('JSON 解析失败:', {
error: error.message,
input: jsonString.substring(0, 100), // 只显示前100个字符
stack: error.stack
});
throw new Error(`无法解析 JSON: ${error.message}`);
}
3.5 错误分类处理
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
// 根据状态码分类处理
if (response.status === 404) {
throw new Error('用户不存在');
} else if (response.status === 500) {
throw new Error('服务器错误');
} else {
throw new Error(`请求失败: ${response.status}`);
}
}
return await response.json();
} catch (error) {
// 网络错误
if (error.name === 'TypeError' && error.message.includes('fetch')) {
console.error('网络连接失败');
throw new Error('请检查网络连接');
}
// 其他错误
throw error;
}
}
四、异步错误处理的最佳实践
4.1 Promise 链式调用
// ✅ 在 Promise 链的末尾添加 catch
fetch('/api/data')
.then(response => response.json())
.then(data => processData(data))
.then(result => displayResult(result))
.catch(error => {
// 统一处理所有错误
console.error('操作失败:', error);
showErrorMessage(error.message);
});
4.2 async/await 错误处理
// ✅ 使用 async/await + try/catch
async function fetchAndProcess() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const processed = await processData(data);
return processed;
} catch (error) {
console.error('处理失败:', error);
// 可以返回默认值或重新抛出
throw error;
}
}
4.3 错误边界模式
// ✅ 创建错误边界函数
function withErrorBoundary(fn, fallback) {
return async function(...args) {
try {
return await fn(...args);
} catch (error) {
console.error('错误边界捕获:', error);
return fallback ? fallback(error) : null;
}
};
}
// 使用示例
const safeFetch = withErrorBoundary(
fetch,
(error) => ({ error: error.message, data: null })
);
const result = await safeFetch('/api/data');
五、面试要点总结
核心知识点
-
try-catch的局限性:- ✅ 只能捕获同步错误
- ✅ 可以捕获
await后的异步错误 - ❌ 无法捕获 Promise 链中的错误(需用
.catch()) - ❌ 无法捕获
setTimeout等异步回调中的错误
-
异步错误处理方式:
- Promise 链:使用
.catch() - async/await:使用
try/catch+await - 全局监听:
unhandledrejection事件
- Promise 链:使用
-
最佳实践:
- 避免滥用
try-catch控制流程 - 局部捕获,提供有意义的错误信息
- 使用
finally进行资源清理 - 分类处理不同类型的错误
- 避免滥用
常见面试题
Q1: try-catch 能捕获异步错误吗?
答:需要分情况:
- ❌ 不能捕获
setTimeout、Promise.then()等异步回调中的错误 - ✅ 可以捕获
await后的异步错误 - ✅ 可以捕获同步错误
Q2: 如何捕获 Promise 中的错误?
答:
- 使用
.catch()方法 - 使用
async/await + try/catch - 全局监听
unhandledrejection事件
Q3: 如何实现资源加载失败的重试机制?
答:
- 监听
error事件 - 根据资源类型(IMG、SCRIPT 等)判断
- 实现重试逻辑或切换到备用资源
- 使用 Promise 封装,支持多次重试
实战建议
- ✅ 异步操作统一使用 Promise 或 async/await
- ✅ 在 Promise 链末尾添加
.catch() - ✅ 在应用入口添加全局错误监听
- ✅ 提供有意义的错误信息和错误分类
- ✅ 使用
finally确保资源清理
前端面试小册 文章被收录于专栏
每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

MDPI工作强度 472人发布
查看6道真题和解析