异步错误处理
异步错误处理的挑战
JavaScript的异步特性让错误处理变得复杂。回调函数、Promise和async/await各自有不同的错误处理机制。未捕获的异步错误可能导致程序静默失败,这种隐蔽性使得调试变得困难。
// 典型的未处理异步错误示例
setTimeout(() => {
throw new Error('异步错误未被捕获');
}, 1000);
回调函数的错误处理
在传统的回调模式中,错误通常作为第一个参数传递。这种约定俗成的模式称为"错误优先回调"(Error-first callbacks)。
fs.readFile('不存在的文件.txt', (err, data) => {
if (err) {
console.error('读取文件出错:', err.message);
return;
}
console.log('文件内容:', data);
});
常见问题包括忘记检查错误参数、在回调中抛出同步错误未被捕获等。更复杂的嵌套回调还会导致"回调地狱",使错误处理逻辑分散。
Promise的错误处理机制
Promise提供了更结构化的错误处理方式。通过.catch()
方法或.then()
的第二个参数可以捕获错误。
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('网络响应不正常');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => {
console.error('请求失败:', error);
});
Promise链中的错误会一直向下传递,直到遇到.catch()
处理器。未处理的Promise拒绝会导致UnhandledPromiseRejectionWarning。
// 未处理的Promise拒绝
new Promise((resolve, reject) => {
reject(new Error('这个错误未被捕获'));
});
async/await的错误处理
async/await语法让异步代码看起来像同步代码,但错误处理仍需特别注意。最常用的方式是try/catch块。
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('获取数据失败:', error);
}
}
对于并行操作,错误处理需要更细致:
async function fetchMultiple() {
try {
const [res1, res2] = await Promise.all([
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2')
]);
// 处理结果...
} catch (error) {
// 任一请求失败都会进入这里
console.error('请求失败:', error);
}
}
全局错误处理
对于未捕获的异步错误,可以设置全局处理器:
// Node.js环境
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
});
// 浏览器环境
window.addEventListener('unhandledrejection', event => {
console.error('未处理的Promise拒绝:', event.reason);
event.preventDefault(); // 防止默认错误输出
});
错误边界与传播
在复杂应用中,需要考虑错误的传播策略。有时需要在局部处理错误,有时则需要将错误冒泡到上层。
async function processUserData(userId) {
try {
const user = await getUser(userId);
const profile = await getProfile(user.id);
return { user, profile };
} catch (error) {
if (error instanceof NetworkError) {
// 网络错误特殊处理
await logNetworkError(error);
throw new RetryableError('处理可重试错误');
}
throw error; // 其他错误继续向上传播
}
}
错误类型与自定义错误
创建特定的错误类型有助于更精确的错误处理:
class ApiError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.name = 'ApiError';
}
}
async function callApi() {
try {
const response = await fetch('/api');
if (!response.ok) {
throw new ApiError('API请求失败', response.status);
}
return await response.json();
} catch (error) {
if (error instanceof ApiError && error.statusCode === 404) {
// 特殊处理404错误
return fallbackData;
}
throw error;
}
}
错误日志与监控
生产环境中,需要将错误记录到日志系统或错误监控平台:
async function logError(error) {
try {
await fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
})
});
} catch (loggingError) {
console.error('记录错误失败:', loggingError);
}
}
window.addEventListener('error', event => {
logError(event.error);
});
window.addEventListener('unhandledrejection', event => {
logError(event.reason);
});
测试异步错误
编写测试时,需要验证代码是否正确地抛出和处理错误:
// 使用Jest测试框架示例
describe('异步函数错误处理', () => {
test('应该抛出特定错误', async () => {
await expect(failingAsyncFunction()).rejects.toThrow(ExpectedError);
});
test('应该正确处理错误', async () => {
const result = await functionThatHandlesError();
expect(result).toBe(fallbackValue);
});
});
性能考量
错误处理逻辑可能影响性能,特别是在热路径中:
// 低效的错误处理
async function processItems(items) {
const results = [];
for (const item of items) {
try {
results.push(await process(item));
} catch (error) {
results.push(null);
}
}
return results;
}
// 更高效的错误处理
async function processItems(items) {
return Promise.allSettled(items.map(item =>
process(item).catch(() => null)
));
}
浏览器与Node.js差异
不同环境下的异步错误处理存在差异:
// 浏览器中的Web Worker错误处理
const worker = new Worker('worker.js');
worker.onerror = (event) => {
console.error('Worker错误:', event.message);
};
// Node.js中的子进程错误处理
const { spawn } = require('child_process');
const child = spawn('some-command');
child.on('error', (error) => {
console.error('子进程错误:', error);
});
第三方库集成
与第三方库集成时,需要考虑其错误处理约定:
// Axios的错误处理
axios.get('/api/data')
.then(response => console.log(response.data))
.catch(error => {
if (error.response) {
// 服务器响应了非2xx状态码
console.error('API错误:', error.response.status);
} else if (error.request) {
// 请求已发出但无响应
console.error('网络错误:', error.message);
} else {
// 其他错误
console.error('配置错误:', error.message);
}
});
错误恢复策略
根据错误类型实施不同的恢复策略:
async function withRetry(fn, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
return await fn();
} catch (error) {
attempt++;
if (attempt >= maxRetries || !isRetryable(error)) {
throw error;
}
await delay(1000 * attempt); // 指数退避
}
}
}
async function fetchWithRetry() {
return withRetry(() => fetch('https://api.example.com/data'));
}
错误处理的架构设计
在大型应用中,可以建立中央错误处理机制:
// 错误处理中间件示例(Express.js)
app.use(async (err, req, res, next) => {
await logError(err);
if (err instanceof ValidationError) {
return res.status(400).json({ error: err.message });
}
if (err instanceof AuthError) {
return res.status(401).json({ error: '未授权' });
}
res.status(500).json({ error: '服务器错误' });
});
前端UI的错误处理
在前端界面中,需要将错误友好地展示给用户:
async function loadUserProfile() {
try {
setLoading(true);
const profile = await fetchProfile();
setProfile(profile);
} catch (error) {
setError(
error instanceof NetworkError
? '网络连接失败,请检查您的网络'
: '加载个人资料时出错'
);
} finally {
setLoading(false);
}
}
内存泄漏防范
不当的错误处理可能导致内存泄漏:
// 可能导致泄漏的代码
function setupLeakyHandler() {
const hugeObject = new Array(1e6).fill('data');
someEventEmitter.on('event', () => {
throw new Error('这个错误未被捕获');
// hugeObject 不会被释放
});
}
// 改进版本
function setupSafeHandler() {
const hugeObject = new Array(1e6).fill('data');
someEventEmitter.on('event', () => {
try {
throw new Error('这个错误被捕获');
} catch (error) {
console.error(error);
}
// hugeObject 可以被垃圾回收
});
}
错误处理与事务
在需要原子性操作时,错误处理尤为关键:
async function transferFunds(from, to, amount) {
let connection;
try {
connection = await db.getConnection();
await connection.beginTransaction();
await withdraw(from, amount, connection);
await deposit(to, amount, connection);
await connection.commit();
} catch (error) {
if (connection) {
await connection.rollback();
}
throw error;
} finally {
if (connection) {
connection.release();
}
}
}
错误处理模式比较
不同场景下适用的错误处理模式:
// 模式1:直接处理
async function mode1() {
try {
const result = await operation();
// 处理结果...
} catch (error) {
// 直接处理错误
handleError(error);
}
}
// 模式2:转换错误
async function mode2() {
try {
const result = await operation();
// 处理结果...
} catch (error) {
// 转换错误类型后重新抛出
throw new ApplicationError('操作失败', { cause: error });
}
}
// 模式3:静默失败
async function mode3() {
try {
const result = await operation();
// 处理结果...
} catch (error) {
// 记录但不中断程序流
logError(error);
return defaultValue;
}
}
异步生成器的错误处理
异步生成器函数需要特殊的错误处理方式:
async function* asyncGenerator() {
try {
yield await firstStep();
yield await secondStep();
} catch (error) {
console.error('生成器内部错误:', error);
yield fallbackValue;
}
}
// 使用时的错误处理
async function consumeGenerator() {
const generator = asyncGenerator();
try {
for await (const value of generator) {
console.log(value);
}
} catch (error) {
console.error('消费生成器时出错:', error);
}
}
WebSocket的错误处理
实时连接的错误处理需要考虑连接状态:
function setupWebSocket(url) {
const socket = new WebSocket(url);
socket.onerror = (event) => {
console.error('WebSocket错误:', event);
scheduleReconnect();
};
socket.onclose = (event) => {
if (event.wasClean) {
console.log(`连接正常关闭: ${event.code} ${event.reason}`);
} else {
console.error('连接异常断开');
scheduleReconnect();
}
};
let reconnectTimer;
function scheduleReconnect() {
clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(() => {
setupWebSocket(url); // 重新连接
}, 5000);
}
}
错误处理与性能监控
将错误处理与性能监控结合:
async function trackedOperation(operationName, fn) {
const startTime = performance.now();
try {
const result = await fn();
trackSuccess(operationName, performance.now() - startTime);
return result;
} catch (error) {
trackError(operationName, error, performance.now() - startTime);
throw error;
}
}
// 使用示例
async function fetchUserData() {
return trackedOperation('fetchUserData', () => fetch('/api/user'));
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn