nodejs异步控制「co、async、Q 、『es6原生promise』、then.js、bluebird」有何优缺点?最爱哪个?哪个简单?

co:npmjs.org/package/co es6 promise:es6.ruanyifeng.com/# ---------------------------- 2014/12/19:co 出了 4.0
关注者
1114
被浏览
128072
要说简单,async 是最简单的,只是在 callback 上加了一些语法糖而已。在不是很复杂的用例下够用了,前提是你已经习惯了 callback 风格的写法。

then.js 上手也是比较简单的,因为也是基于 callback 和 continuation passing,并不引入额外的概念,比起 async,链式 API 更流畅,个人挺喜欢的。我挺久以前写过一个在 Node 里面跑 shell 命令的小工具,思路差不多:npmjs.org/package/shell

Callback-based 方案的最大问题在于异常处理,每个 callback 都得额外接受一个异常参数,发生异常就得一个一个往后传,异常发生后的定位很麻烦。

ES6 Promise, Q, Bluebird 核心都是 Promise,缺点嘛就是必须引入这个新概念并且要用就得所有的地方都用 Promise。对于 Node 的原生 API,需要进行二次封装。Q 和 Bluebird 都是在实现 Promise A+ 标准的基础上提供了一些封装和帮助方法,比如 Promise.map 来进行并行操作等等。Promise 的一个问题就是性能,而 Bluebird 号称速度是所有 Promise 库里最快的。ES6 Promise 则是把 Promise 的包括进 js 标准库里,这样你就不需要依赖第三方实现了。

关于 Promise 能够如何改进异步流程,建议阅读:html5rocks.com/en/tutor

co 是 TJ 大神基于 ES6 generator 的异步解决方案。要理解 co 你得先理解 ES6 generator,这里就不赘述了。co 最大的好处就是能让你把异步的代码流程用同步的方式写出来,并且可以用 try/catch:

co(function *(){
  try {
    var res = yield get('http://badhost.invalid');
    console.log(res);
  } catch(e) {
    console.log(e.code) // ENOTFOUND
 }
})()

但用 co 的一个代价是 yield 后面的函数必须返回一个 Thunk 或者一个 Promise,对于现有的 API 也得进行一定程度的二次封装。另外,由于 ES6 generator 的支持情况,并不是所以地方都能用。想用的话有两个选择:

1. 用支持 ES6 generator 的引擎。比如 Node 0.11+ 开启 --harmony flag,或者直接上 iojs;
2. 用预编译器。比如 Babel (babeljs.io/) , Traceur (github.com/google/trace) 或是 Regenerator (github.com/facebook/reg) 把带有 generator 的 ES6 代码编译成 ES5 代码。

(延伸阅读:基于 ES6 generator 还可以模拟 go 风格的、基于 channel 的异步协作:Taming the Asynchronous Beast with CSP in JavaScript

但是 generator 的本意毕竟是为了可以在循环过程中 yield 进程的控制权,用 yield 来表示 “等待异步返回的值” 始终不太直观。因此 ES7 中可能会包含类似 C# 的 async/await :

async function showStuff () {
  var data = await loadData() // loadData 返回一个 Promise
  console.log(data) // data 已经加载完毕
}

async function () {
  await showStuff() // async 函数默认返回一个 Promise, 所以可以 await 另一个 async 函数
  // 这里 showStuff 已经执行完毕
}

可以看到,和用 co 写出来的代码很像,但语意上更清晰。因为本质上 ES7 async/await 就是基于 Promise + generator 的一套语法糖。深入阅读:ES7 async functions

想要今天就用 ES7 async/await 也是可以的!Babel 的话可以用配套的 asyncToGenerator transform: babeljs.io/docs/usage/t
Traceur 和 Regenerator 对其也已经有实验性的支持了。

另外,Fiber 其实也是一个不错的抽象,只可惜和 ES 目前的发展不一致,不太可能应用到浏览器端。Fiber 和 async/await 的相似处在于程序逻辑可以用接近同步的方式表现,但应用了 Fiber 之后异步和同步的方法调用是看不出区别的。Meteor 框架内部就大量应用了 Fiber。

---

2015 年 10 月更新:
- async/await 已经升级为 stage 3 proposal,纳入正式规范指日可待
- Microsoft Edge 已经率先原生支持 async/await:JavaScript goes to Asynchronous city