【第21期】await-to-js

1,119 阅读2分钟

原文链接

今天分析的源码是 await-to-js, 该包的作者受困于对 ES7中的 await 的错误的捕获还需要使用 try/catch的方式, 因此借鉴于 golang 里的错误处理的简洁模式, 开发了这个包.

本包的使用方式如下

async function asyncTaskWithCb(cb) {
     let err, user, savedTask, notification;
​
     [ err, user ] = await to(UserModel.findById(1));
     if(!user) return cb('No user found');
​
     [ err, savedTask ] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
     if(err) return cb('Error occurred while saving task');
​
    if(user.notificationsEnabled) {
       [ err ] = await to(NotificationService.sendNotification(user.id, 'Task Created'));
       if(err) return cb('Error while sending notification');
    }
​
    if(savedTask.assignedUser.id !== user.id) {
       [ err, notification ] = await to(NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you'));
       if(err) return cb('Error while sending notification');
    }
​
    cb(null, savedTask);
}

当然了, 作者也不是建议在所有的地方都使用这个包 如果比较简单的话直接使用 try/cathc 也是可以的.

源码

全部代码非常简短, 本质上是将入参 promise 的结果进行了二次处理, 通过元组类型统一了返回格式:

如果成功则返回 [null, T]; 如果失败则返回 [err, undefined]. 同样还提供了第二个参数 errorExt来实现自定义错误

// 定义了两个泛型 T, U(默认为 Error类型)
export function to<T, U = Error> (
  promise: Promise<T>,
  errorExt?: object
   // 元组类型+联合类型 定义了to的返回值类型
): Promise<[U, undefined] | [null, T]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[U, undefined]>((err: U) => {
      if (errorExt) {
        const parsedError = Object.assign({}, err, errorExt);
        return [parsedError, undefined];
      }
​
      return [err, undefined];
    });
}
​
export default to;

疑惑之处

通过对源码的阅读以及测试, 我发现一个问题, 对于自定义错误或者用作者的话是 Additional Information you can pass to the err object , 那么就我的理解, 我传入 errorExt?: object的时候会用对象的形式 {'msg': 'hhhh'}

if (errorExt) {
   const parsedError = Object.assign({}, err, errorExt);
   return [parsedError, undefined];
}

在源码中的例子中 examples\example-1.js, 它使用的是 reject('Data is missing') 的方式, 所以源码中 U 的类型为 string, 而打印合并后的对象 parsedError 的值如下, 而这样的值 并不是我们想要的.

{
  '0': 'h',
  '1': 'h',
  '2': 'h',
  '3': 'h',
  '4': ' ',
  '5': 'i',
  '6': 's',
  '7': ' ',
  '8': 'm',
  '9': 'i',
  '10': 's',
  '11': 's',
  '12': 'i',
  '13': 'n',
  '14': 'g'
}

如果改成使用 Error 的形式返回, 即 reject(Error("Data is missing")), 那么实际上 const parsedError = Object.assign({}, err, errorExt); 也是无法合并的, 实际测试结果如下

e = Error('error')
Object.assign({}, e)
output: {}
​
Object.assign({},e ,{msg:'hhhh'})
output: {msg: 'hhhh'}

因此我觉得源码这里的处理是有问题的.

为此我有两种想法, 第一种是针对 err 的类型做出判断然后再处理, 不过这样的方式比较麻烦

第二种是修改了一下返回类型, U => {err: U}, 即将err 包裹成对象

export function to<T, U = Error>(
  promise: Promise<T>,
  errorExt?: object
): Promise<[{ err: U }, undefined] | [null, T]> {
  return promise
    .then<[null, T]>((data: T) => [null, data])
    .catch<[{ err: U }, undefined]>((err: U) => {
      if (errorExt) {
        const parsedError = Object.assign({}, { err }, errorExt);
        return [parsedError, undefined];
      }
​
      return [{ err }, undefined];
    });
}
​
export default to;

我个人认为第二种方法比较合适, 不知道大家有什么想法, 欢迎交流~