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

co:https://www.npmjs.org/package/co es6 promise:http://es6.ruanyifeng.com/#…
关注者
1,207
被浏览
186,250

24 个回答

要说简单,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

如果着眼于现在和未来一段时间的话,建议用Promise,不论是ES6的还是用Q还是用bluebird,毫无疑问立即马上开始用。

如果眼光放长一点看的话,用了co以后基本上再也不愿回去了,即使是“正宫娘娘”Promise。

这co完全就是用yield/generator实现了async/await的效果啊,第一次见的时候,真是有种天马行空的感觉(原谅我见识少)。

它不仅能够“同步非阻塞”,也几乎没有剥夺我对多个非阻塞操作依赖关系或者竞争关系的精确控制,当我需要精确控制异步流程的时候,回去用Promise甚至callback,当我需要写的爽,一泻千里的时候,用async/await(扯远了)。

当然它还有一些不足,这需要实现一个类似C#里的Task库的功能,或者是用别的思路,比如CSP,这真是一个完全打破用观察者的方式来处理GUI事件的新思路,让处理GUI事件变得完全用消息的产生和消费的思路了(又特么扯远了)。

但不管怎样,co提供了一个新的方向,直到Promise,还是“异步”,说到底还是在用“回调”的思路来实现“异步非阻塞”,只是async.js, Promise,它们把回调嵌套“拉平”了,我觉得它们解决的是“回调嵌套的问题”,而不是“回调的问题”。

但到了async/await,包括co模拟出来的,它都打破了“异步”的僵局,实现了“同步非阻塞”,是的这就是大家一直都在说的异步不一定要回调,非阻塞不一定要异步。<del>co,async/await和响马的fibjs一起往go看齐了,难怪tj去搞go去了</del>。