await-to-js

800 阅读4分钟

看源码还是需要知道这个代码主要是干什么的,解决了什么样的问题,这样才可以深入的了解源码。

await-to-js的来历

从官方的角度来说它就是一个async、await 包装器,便于错误处理,那么为什么要这样做呢? 首先看一下官方的例子:

function AsyncTask() {
   asyncFuncA(function(err, resultA){
      if(err) return cb(err);

      asyncFuncB(function(err, resultB){
         if(err) return cb(err);

          asyncFuncC(function(err, resultC){
               if(err) return cb(err);

               // And so it goes....
          });
      });
   });
}

才开始我们写异步代码是像上面一样的,造成了地狱回调的问题,导致代码维护困难,那么后面有了promise,我们的代码变成了下面的:

function asyncTask(cb) {

   asyncFuncA.then(AsyncFuncB)
      .then(AsyncFuncC)
      .then(AsyncFuncD)
      .then(data => cb(null, data)
      .catch(err => cb(err));
}

上面的代码简洁了很多而且维护性也好了很多,本身存在all方法,所以可以支持多个并发请求,获取并发请求中数据,解决了地狱回调的问题。相比较于上面的回调地狱,使用Promise可以帮助我们让代码只在纵向发展,并且提供了处理错误的回调。显然优雅了很多。不过就算Promise已经这么优秀了,可是依然存在两个美中不足的地方。

  • 不够同步(代码依然会纵向延伸)。
  • 不能很方面的给每一次异步操作都进行错误处理。

当然在我看来存在的问题就是如果不设置回调函数,promise内部抛出的错误,不会反应给外部。另外一个就是存在多个异步依赖调用的时候,后面的执行依赖前面的结果,这个时候一个是不断往下传递,或设置全局变量来接受,这样可能就比较复杂。

这就带来了解决方案async/await,那么它的代码就可以改写为:

function async asyncTask(cb) {
    const asyncFuncARes = await asyncFuncA()
    const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
    const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)

async/await看起来就相对简洁明了很多了,并且async返回的还是Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。 如果为上面的代码加上异常处理,那么代码就变成如下:

function async asyncTask(cb) {
      try {
        const asyncFuncARes = await asyncFuncA()
      } catch(error) {
        return new Error(error)
      }
      try {
        const asyncFuncBRes = await asyncFuncB(asyncFuncARes)
        return new Error(error)
      }
      try {
        const asyncFuncCRes = await asyncFuncC(asyncFuncBRes)
      } catch(error) {
        return new Error(error)
      }
}

看上去有了异常处理,代码基本就完善了,但是为每一个await都添加try/catch这样就显得太过于臃肿了,那么要简化就需要提到今天的主角await-to-js了。

await-to-js用法

import to from 'await-to-js';
function async asyncTask() {
     const [err, asyncFuncARes]  = await to(asyncFuncA())
     if(err) throw new (error);
     
     const [err, asyncFuncBRes]  = await to(asyncFuncB(asyncFuncARes))
     if(err) throw new (error);
     
     const [err, asyncFuncCRes]  = await to(asyncFuncC(asyncFuncBRes)
     if(err) throw new (error);
}

上面的用法是不是就简洁多了,没有了try/catch,看着也方便了很多,其实就是使用to这个函数将try/catch替换掉了,返回了两个值。

await-to-js源码分析

// src/await-to-js.ts
/**
 * @param { Promise } promise
 * @param { Object= } errorExt - Additional Information you can pass to the err object
 * @return { Promise }
 */
export function to<T, U = Error> (
  promise: Promise<T>,
  errorExt?: object
): 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;

上面的代码是ts的,代码很简单,需要两个参数一个是promise类型的,一个error类型的,其实这里要求第一个参数是promise类型的就和上面我所讲的async函数返回的是一个promise对象对应上了。有可能看起来很奇怪,那么写成js版本就是:

export function to(promise, errorExt) {
      return promise
          .then((data) => [null, data]) // 成功,返回[null,响应结果]
          .catch((err) => {
          if (errorExt) {
              const parsedError = Object.assign({}, err, errorExt);
              return [parsedError, undefined]; // 失败,返回[错误信息,undefined]
          }
          return [err, undefined];
      });
}
export default to;

上次之所以要传入错误对象,其实就是这玩意拿来用户自定义错误信息的,通过Object.assign将正常返回的error和用户自定义和合并到一个对象里面供用户自己选择。

总结

其实源码很少,也不难,主要里面牵扯的前因后果很多,里面主要还是对promise的理解和使用,如果对promise认识足够深入,其实这一整套就很简单了,所以我基本又把阮一峰老师的promise,async异步编程刷了一遍,毕竟现在很多源码和项目基本都牵扯了上面的两个知识点,如果不弄明白或许很多源码都将会都不明白。