🦢前端更优雅地进行异步错误处理

259 阅读2分钟

异步操作占据日常业务编码的大部分时间,本文将提出一种优雅的异步操作方案。使得 Promise 再也无需 try-catch,让异步编程分支更流畅,开发体验更佳。而且逼迫开发者必须将异常放到第一位去考虑,避免忘记 try-catch 导致出现大量的 Unhandled Promise Rejection

/**
 * Promise 再也无需 try catch,让异步编程分支更流畅,开发体验更佳。
 * 而且逼迫开发者必须将异常放到第一位去考虑,避免忘记 try catch 导致出现大量的 Unhandled Promise rejection
 *
 * @param promise
 * @returns 返回 tuple,失败则第一项是 error,成功第一项为 null,第二项是返回值。
 */
export async function box<T>(
  promise: Promise<T>,
): Promise<[Err: (Error & T) | null, Res: T | null]> {
  try {
    return [undefined, await promise]; // 返回 undefined 而不是 null 是为了支持解构默认值
  } catch (error) {
    return [error, undefined];
  }
}

版本 1

try {
  const resp = await fetchList();
  // do a lot of processing with resp
  // do a lot of processing with resp
  // do a lot of processing with resp
  // do a lot of processing with resp
} catch (error) {
  // 处理错误
  logger.error(error);
}

版本 1 违反了『对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题』。

版本 2

仅对请求 try-catch

let resp: IListResp; // 需要显示申明类型,否则类型将是 any

try {
  resp = await fetchList();
} catch (error) {
  // 处理错误
  logger.error(error);
  
  return; // 错误处理完毕立即退出
}

// do a lot of processing with resp
// do a lot of processing with resp
// do a lot of processing with resp
// do a lot of processing with resp

版本 2 有一个缺陷:需要显示申明类型,否则返回值类型将是 any。

版本 3

const [error, resp] = await box(fetchList()); // error 和 resp 自动推断类型

if (error) {
  // 处理错误
  logger.error(error);
  
  return;
}

// do a lot of processing with resp
// do a lot of processing with resp
// do a lot of processing with resp
// do a lot of processing with resp

版本 3 将错误放到第一位,错误处理不可能被忘记而且错误处理更自然,类似 go 语言。