async 库封装了几个非常常见的异步模式,包括并行进行任意异步调用和异步迭代列表。它旨在与“nodeback”(err, res) API 一起使用,这使得它对许多 Node.js 应用程序都很有用。然而async 是一个特定 解决方案,它只简化了库中包含的异步模式。
相比之下,在我看来,Promises 是针对异步代码问题的更通用的解决方案。乍一看,它们不仅提供了错误冒泡和扁平化回调金字塔的明显好处,而且可以更简单地解决原本需要各种复杂模式 async 封装的问题。
我将通过快速浏览async 的一些可用模式来演示这一点。例如,async.waterfall 函数是这样使用的:
async.waterfall([
function (cb) {
asyncCall('argument', cb);
},
function(resultOfFirstCall, cb) {
anotherCall(resultOfFirstCall, 'someOtherArgument' cb);
},
], function(err, res) {
if (err) handle(err);
useFinalResult(res);
});
在大多数 Promise 库中没有与 async.waterfall 等效的东西(或者至少在 Q 中没有),因为使用 Array.reduce 从头开始实现它是如此简单,就像这样(基于 Q 的示例,但在其他 Promise 库中几乎相同):
[
function() {
return asyncCall('argument');
},
function(resultOfFirstCall) {
return anotherCall(resultOfFirstCall, 'someOtherArgument');
}
].reduce(Q.when, Q())
.then(useFinalResult, handle);
async中的其他大函数包括async.parallel,其中Q包括Q.all:
// async
async.parallel([
asyncFunc,
asyncFunc2
], function(err, res) {
if (err) handle(err);
useFinalResult(res);
// res[0] === asyncFuncResult
// res[1] === asyncFunc2Result
});
// Q
Q.all([
asyncFunc(),
asyncFunc2()
]).then(useFinalResult, handle);
还有async.map。当您使用 Promise 时,您实际上不需要 async.map,因为普通的 Array.map 就足够了:
// async
async.map(['file', 'file2', 'file3'], fs.stat, function(err, res) {
if (err) handle(err);
useFinalResult(res);
});
// Q
Q.all(['file', 'file2', 'file3']
.map(Q.nfbind(fs.stat)))
.then(useFinalResult, handle);
async 的其余部分同样易于简洁地实现,只需使用您的 Promise 库中相对简单的部分即可。 (请注意,最后一个示例使用了一个函数 Q.nfbind: nfbind 和 Q 提供的其他 nf* 函数基本上是您在 nodeback API 中使用 Promise 所需的全部内容,因此尝试使用时甚至没有特别大的阻力对期望 nodebacks 的库的承诺。)
最后,是否使用 Promise 或 nodebacks 取决于您,但我认为 Promise 是一种更灵活、更有能力且通常更简洁的方式来实现大多数异步操作。
Callbacks are imperative, promises are functional 值得一读,以了解更多关于这方面的信息。