JavaScript 中异步错误设计方法

343 阅读2分钟

以 nodejs fs 的 stat 方法为例

回调式

nodejs 早起 api 以回调的方式为主,回调函数第二个参数就是错误❌对象(先处理错误原则)

fs.stat("index.mjs", (err, stats) => {
    if (err) {
        console.log(err);
        return 
    }
  console.log("stats", stats);
});

使用 promise

返回一个数组 [err, res], 将错误❌放在数组第一位,异步结果放在第二位,遵循 promise 的设计原则

纯 promise 链式调用 then/catch

fs.promises
  .stat("/stat.tjs")
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.log(err);
  });

then/catch 方法

const [err1, res1] = await fs.promises
  .stat("/stat.tjs")
  .then((data) => [null, data])
  .catch((err) => [err, null]);

await/then

const [err, res] = await fs.promises.stat("./stat.mjs").then(
  (data) => [null, data],
  (err) => [err, null]
);

await/then-catch

const [err, res] = await fs.promises
  .stat("/stat.tjs")
  .then((data) => [null, data])
  .catch((err) => [err, null]);

try-catch/await

try {
  let data = await fs.promises.stat("./stat.js");
} catch (err) {
  console.log("sdf", err);
}

对 promise 进行扩展

如何学习了 Rust, 支持 Rust 中分为 可恢复 / 不可恢复 两种错误;

  • panic! 不可恢复,对应了 javascript 中的 throw a error,典型的有 unwrap, 错误是不可恢复,不可继续传递的。下面在 promise 中使用 unwrap 统一处理错误,减少样板代码。

首先 扩展 Promise interface 避免 TS 类型报错:

// promise.d.ts
interface Promise<T> {
  /**
   * panic a error or return a promise
   * @returns A Promise for the completion of the callback.
   */
  unwrap(): Promise<T>;
}

以 axios 请求为示例,(考虑到 unwrap 可能成为promise的方法,命名仅供参考)

// 实际扩展原型链 unwrap 方法, 保留 this 使用 function 来声明
Promise.prototype.unwrap = function <T>(): Promise<T> {
  return this
    .then((res) => res)
    .catch((err) => {
      throw new Error('promise unwrap failed\n' + err);
    });
};

使用, unwrap 中直接 throw 错误,与 Rust panic! 效果一致,不可恢复

const service = axios.create({
  baseURL: '/api',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
  },
  timeout: 10000,
  responseType: 'json',
  withCredentials: true,
  transformRequest: [(data) => qs.stringify(data)],
});

// 封装 get 请求
export const getRequest = async (url: string, params?: any) => {
  const { data: res } = await service.get(url, { params }).unwrap()
  return res;
};

生成器

生成器在前端使用场景比较少,使用较多的是 redux-saga 中, 生成器使用 try-catch 捕获错误

function* foo() {
  var x = 
  try {
      yield 3;
  } catch (err) {
      console.log(err)
  }
  var y = x.toUpperCase();
  yield y;
}

var it = foo();

it.next(); // { value:3, done:false }

try {
  it.next(42);
} catch (err) {
  console.log(err);
}

值的注意的是:生成可以在函数体中捕获 yield 错误,也可以在 gen.next 中使用 try-catch 捕获错误。