Node.js:何时使用 Promises 与 Callbacks
Posted
技术标签:
【中文标题】Node.js:何时使用 Promises 与 Callbacks【英文标题】:Node.js: When to use Promises vs Callbacks 【发布时间】:2017-12-15 22:11:04 【问题描述】:我正在更新一些较旧的 Node.js 代码。在此过程中,我正在设计新模块以使用旧代码。我现在发现,与我第一次写这篇文章时相比,我更多地依赖于使用 ES6 承诺而不是回调。所以现在我混合了一些返回承诺的函数和一些接受回调的函数——这很乏味。我认为最终应该重构它以使用承诺。但在那之前……
在哪些情况下首选 Promise,哪些情况下首选回调?
是否有任何类型的情况,回调可以比 Promise 更好地处理,反之亦然?
根据我目前所见,我真的看不出有任何理由使用回调而不是承诺。这是真的吗?
【问题讨论】:
Promise 不能多次调用回调,所以对于流等它们是不可用的。对于任何其他异步函数,promise 获胜。 【参考方案1】:它们的存在都是为了解决同一个问题,处理异步函数的结果。
回调往往更冗长,如果您没有积极模块化您的函数,同时协调多个异步请求可能会导致callback hell。错误处理和跟踪往往不那么直接,甚至令人困惑,因为可能有许多 Error 对象都返回到调用堆栈更下方的单个错误。错误,也需要传回给原始调用者,如果在回调链中使用匿名函数,在确定原始错误在哪里抛出时,这也可能会导致一些头疼的问题。回调的好处之一是它们只是普通的旧函数,除了了解异步操作的工作原理之外,不需要任何额外的了解。
Promise 更常见,因为它们需要更少的代码,更易读,因为它们像同步函数一样编写,有一个错误通道,可以处理抛出的错误,并且在最新版本的 Node.js 中添加了util.promisify()
,可以将错误优先回调转换为承诺。还有async/await
,现在也是making its way into Node.js,它们也与Promises 交互。
这完全是基于意见的,所以它确实是关于你最喜欢什么,但是,Promises 和async/await
是回调的演变并增强了异步开发体验。无论如何,这并不是一个详尽的比较,而是对回调和承诺的高级研究。
【讨论】:
听起来你在说使用回调的过程将被promise替换。真的吗?这就是我的问题所在。我真的看不出有任何理由使用回调而不是承诺,但我认为这可能是一种幼稚的观点。所以我想更广泛地了解(如果有的话)何时使用回调以及它们有什么用处。 它们并没有被替换,正在出现更好的替代方案来改进回调的功能,处理异步功能。至于何时使用回调,就像我在回答中所说的那样,这实际上取决于您对 Promises 或async/await
的舒适程度。人们倾向于更喜欢 Promise 的原因我在上面说了等等。我个人建议使用 Promises,如果不是为了社区前进的方向,如果还没有的话。【参考方案2】:
首先,您几乎不想编写混合了异步操作的回调和承诺的代码。如果您要转向 Promise 或引入一些 Promise,那么您可能希望将同一段代码中的回调重构为 Promise。对于合适的操作类型,与普通回调相比,promise 有很多优势,因此在已经在某个代码区域工作时,值得努力转换。
Promise 非常适合:
监控同步操作 只需要通知一次(通常是完成或错误) 协调或管理多个异步操作,例如排序或分支异步操作或同时管理多个运行中的操作 从嵌套或深度嵌套的异步操作中传播错误 为使用 async/await 准备好代码(或现在与转译器一起使用) 符合 Promise 模型的操作只有三个状态:pending
、fulfilled
和 rejected
,并且状态从 pending => fulfilled
或从 pending => rejected
转换的情况不能更改(单个方式转换)。
动态链接或链接异步操作(比如做这两个异步操作,检查结果,然后根据中间结果决定其他哪些异步操作)
管理异步和同步操作的混合
自动捕获并向上传播异步完成回调中发生的任何异常(在普通回调中,这些异常有时会被静默隐藏)。
普通回调适用于 promise 不能做的事情:
同步通知(如Array.prototype.map()
的回调)
可能多次出现的通知(因此需要多次调用回调)。 Promise 是一次性设备,不能用于重复通知。
无法映射到未决、已完成、已拒绝的单向状态模型的情况。
而且,我还要添加EventEmitter
。
EventEmitter 非常适合:
发布/订阅类型通知 带有事件模型的接口,特别是当事件可以发生多次时(如流) 当第 3 方代码想要参与或监视某事而无需任何 API 而不是 eventEmitter 时,松散耦合。无需设计 API。只需公开一个 eventEmitter 并定义一些事件和与之相关的数据。关于将普通回调代码转换为 Promise 的注意事项
如果你的回调符合节点调用约定,回调作为最后一个参数传递并像这样callback(err, result)
调用,那么你在某种程度上自动将父函数包装在一个带有util.promisify()
在node.js 中的promise 中,或者如果使用Bluebird promise library,与Promise.promisify()
。
使用 Bluebird,您甚至可以一次性承诺整个模块(在 node.js 调用约定中使用异步回调),例如:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.writeFileAsync("file.txt", data).then(() =>
// done here
).catch(err =>
// error here
);
在 node.js 版本 8+ 中
现在有util.promisify()
,它将使用 node.js 异步调用约定的异步函数转换为返回承诺的函数。
来自the doc:的示例
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
// usage of promisified function
stat('.').then((stats) =>
// Do something with `stats`
).catch((error) =>
// Handle the error.
);
【讨论】:
【参考方案3】:我不记得我从哪里得到这些东西,但可能有助于更好地理解承诺。
Promise 不是回调。 Promise 代表异步操作的未来结果。当然,按照你的方式编写它们,你得到的好处很少。但是,如果您按照它们的预期使用方式编写它们,您可以以类似于同步代码的方式编写异步代码并且更容易遵循: 优势 1. 回调的可读性 2. 容易捕捉错误。 3.同时回调
1.回调的可读性 Promise 提供了一种更简洁明了的方式来表示 javascript 中的顺序异步操作。它们实际上是实现与回调相同效果的不同语法。优点是增加了可读性。像这样的
aAsync()
.then(bAsync)
.then(cAsync)
.done(finish);
比将每个单独的函数作为回调传递的等价物更具可读性,例如
aAsync(function()
return bAsync(function()
return cAsync(function()
finish()
)
)
);
2。容易发现错误。 当然,代码不会少很多,但可读性要高得多。但这不是结束。让我们发现真正的好处:如果您想检查任何步骤中的任何错误怎么办?用回调来做这件事会很糟糕,但是用 promise 是小菜一碟:
api()
.then(function(result)
return api2();
)
.then(function(result2)
return api3();
)
.then(function(result3)
// do work
)
.catch(function(error)
//handle any error that may occur before this point
);
/* Pretty much the same as a try ... catch block.
Even better: */
api()
.then(function(result)
return api2(); )
.then(function(result2)
return api3(); )
.then(function(result3)
// do work
)
.catch(function(error)
//handle any error that may occur before this point
)
.then(function()
//do something whether there was an error or not
//like hiding an spinner if you were performing an AJAX request.
);
3.同时回调甚至更好: 如果对 api、api2、api3 的这 3 个调用可以同时运行(例如,如果它们是 AJAX 调用)但您需要等待这三个调用会怎样?如果没有承诺,您应该必须创建某种计数器。有了 Promise,使用 ES6 表示法,又是小菜一碟,而且非常简洁:
Promise.all([api(), api2(), api3()])
.then(function(result)
//do work. result is an array containing the values of the three fulfilled promises.
)
.catch(function(error)
//handle the error. At least one of the promises rejected.
);
希望您现在以全新的眼光看待 Promise。
【讨论】:
从这个答案看起来像:***.com/a/22540276以上是关于Node.js:何时使用 Promises 与 Callbacks的主要内容,如果未能解决你的问题,请参考以下文章
Node.js、Promises & Recursion - 可能吗?
Node.js assert.throws 带有异步函数(Promises)