浅谈 co 库

最近在写一个静态博客框架. 但不同于 hexo 之类的, 框架本身提供了博客书写和管理功能. 后端同样使用了 Koa2 来做,

归功于 Arch, 我已经在使用 Node 7.2 了, 不过好像默认还是不能支持 asyncawait , 经查发现是要输入参数开启的=.=. 不过当时第一时间并没有想说去加参数解决, 而是使用了 co 这个库... 虽然也可以用 babel , 但是通过 babel 运行代码很难调试.

node.png

co 用起来其实和 async 差不多, 甚至一些地方更简洁, 比如他可以 yield 一个 Promise 数组. 而 async 则只能 await 一个 Promise.all. 其实归根就是 co 进行了一个隐性的转换. co 内可以直接 yeild 一个数组或对象, co 会自动把数组或对象里面的所有值尝试转为 Promise 并包装在 Promise.all 中返回.

yield [promise1, promise2, ..., promisen]

async 要处理多个异步的并行操作, 只能把这些操作手动放入 Promise.all 返回. 即:

await Promise.all([promise1, promise2, ..., promisen])

草案还有一个 await* , 这个也是最近才注意到的, 使用 await* 就可以这样写了:

await* [promise1, promise2, ..., promisen]

然而草案并不推荐 await* , 目前 Node 7.2 也不支持 await* , 不过 babel 支持 ~

co 的代码其实刚接触异步的时候我就去看了下, 当然当时肯定看不懂. 不过现在回过来看感觉很明了. 其实 co 的实现思路也很简单. co 接收一个 generator, 然后他就可以自动的运行这个 generator 而不需要手动的 yield .

co 的实现简要过程:

  1. 接受一个 generator
  2. generator 封装进 promise
  3. 调用该 generator
  4. 判断是否可以 next, 如果不可以直接 resolve, 否则下一步
  5. 执行 next 操作并取得 next 后的 generator
  6. 判断是否已经 done, 如果是则直接 resolve, 否则下一步
  7. 递归调用第 2 步

参考以上自己可以结合 co 源码看下. 看看是不是就这么回事.

co 内部有一个方法来对 yield 后面的东西进行处理:

function toPromise(obj) {
  if (!obj) return obj;
  if (isPromise(obj)) return obj;
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}

我觉得 yield 可以接一个数组这点很好, 相比起来 async 则要接一个 Promise.all 来处理多个异步的同时运行处理.

function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}

另外, 它也可以接收对象, 这个不是普通的对象, 这个对象里面的值也会执行上面的 toPromise 方法, 然后用 Promise.all 包装后返回.

这样来看其实 co 除了语义没有 async-await 好其实很多方面都更胜一筹.

最后还有一个 co.wrap 方法, 它用来封装一个 generator.

co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
}

其实就是把 generator 放在一个方法里面, 当这个方法调用时立即通过 co 自动启动该 generator 而已. 把这个方法返回了用于导出或者其他用途都可以咯.

真的短小精悍... 膜拜 TJ 大神... 但, async-await 势不可挡, 该用还是得用 0.0, 打算把 co 全部替换为 async 去了. 不过 co 这个库的思想和实现还是可以好好看一看滴.