阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 异步错误处理

异步错误处理

作者:陈川 阅读数:28638人阅读 分类: JavaScript

异步错误处理的挑战

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

上一篇:条件规则组

下一篇:异步流程控制

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌