Node.js:何时使用Promises vs Callbacks

时间:2017-07-11 17:52:02

标签: node.js callback promise es6-promise

我有一些我正在更新的旧Node.js代码。在这个过程中,我正在设计新模块以使用旧代码。我现在发现,与我第一次写这篇文章相反,我更依赖于使用ES6承诺而不是回调。所以现在我有一些函数的混合返回promises和一些回调 - 这是乏味的。我认为最终应该重构使用promises。但在此之前......

首选承诺的首选情况以及首选回调的情况是什么?

回调可以比承诺更好地处理任何类型的情况,反之亦然吗?

根据我到目前为止看到的情况,我真的看不出任何使用回调而不是承诺的理由。这是真的吗?

3 个答案:

答案 0 :(得分:30)

首先,您几乎不想编写混合回调和承诺进行异步操作的代码。如果你正在转向承诺或引入一些承诺,那么你可能想要将同一部分代码中的回调重构为promises。对于适当类型的操作,承诺相对于普通回调有很多优点,在已经在代码区域工作时转换的努力是值得的。

承诺非常适合:

  • 监控同步操作
  • 只需要通知一次(通常是完成或错误)
  • 协调或管理多个异步操作,例如排序或分支异步操作或同时管理飞行中的多个操作
  • 从嵌套或深层嵌套的异步操作传播错误
  • 准备好使用async / await的代码(或现在使用它与转换器一起使用)
  • 符合Promise模型的操作,其中只有三种状态:pendingfulfilledrejected以及状态从pending => fulfilled或{{1}转换的位置然后可以不改变(单个单向转换)。
  • 动态链接或链接异步操作(例如执行这两个异步操作,检查结果,然后根据中间结果确定要执行的其他异步操作)
  • 管理异步和同步操作的混合
  • 自动捕获并向上传播异步完成回调中发生的任何异常(在普通回调中,这些异常有时会被隐藏隐藏)。

普通回调适用于承诺无法做到的事情:

  • 同步通知(例如pending => rejected的回调)
  • 可能多次出现的通知(因此需要多次调用回调)。 Promise是一次性设备,不能用于重复通知。
  • 无法映射到待处理,已履行,被拒绝的单向状态模型的情况。

而且,我还要添加Array.prototype.map()

EventEmitters非常适合:

  • 发布/订阅类型通知
  • 具有事件模型的接口,特别是当事件可以多次发生时(如流)
  • 当第三方代码想要参与或监视某些内容而不使用任何API而不是eventEmitter时,松散耦合。没有API可供设计。只需将eventEmitter设为public并定义一些事件以及随之而来的数据。

关于将普通回调代码转换为Promises的说明

如果你的回调符合调用约定的节点,并且回调作为最后一个参数传递并且像EventEmitter那样调用,那么你有点自动将父函数包装在node.js中带有callback(err, result)的promise中或使用Bluebird promise library时使用Promise.promisify()

使用Bluebird,您甚至可以立即宣传整个模块(在node.js调用约定中使用异步回调),例如:

util.promisify()

在node.js版本8 +

现在有const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs')); fs.writeFileAsync("file.txt", data).then(() => { // done here }).catch(err => { // error here }); 将使用node.js异步调用约定的异步函数转换为返回promise的函数。

来自the doc:

的示例
util.promisify()

答案 1 :(得分:5)

它们都存在解决同样的问题,处理异步函数的结果。

回调往往更加冗长,如果您没有主动模块化您的功能,并发协调多个异步请求会导致callback hell。错误处理和跟踪往往不那么直接甚至令人困惑,因为可能有许多Error对象都会回到调用堆栈中的单个错误。错误,也需要传递回原始调用者,如果在回调链中使用匿名函数,则在确定抛出原始错误的位置时也会导致头部刮擦。回调的好处之一是它们只是简单的旧功能,除了知道异步操作如何工作之外,不需要任何额外的理解。

Promise更常见,因为它们需要更少的代码,更具可读性,因为它们像同步函数一样编写,具有单个错误通道,可以处理抛出的错误并在最新版本的Node中添加util.promisify()。 js,可以将Error-First Callbacks转换为promises。现在还有async/await making its way into Node.js,它们也与Promises接口。

这完全基于意见,所以它确实是关于你最熟悉的东西,但Promises和async/await是回调的演变并增强了异步开发体验。这不是一种详尽的比较,而是对回调和承诺的高级评价。

答案 2 :(得分:1)

我不记得从哪里得到这些东西,但可能有助于更好地理解承诺。

承诺不是回调。 promise表示异步操作的未来结果。当然,按照你的方式写它们,你得到的好处不大。但是如果按照它们的使用方式编写它们,您可以以类似于同步代码的方式编写异步代码,并且更容易遵循: 的优点 1.回调的可读性 2.容易发现错误。 3.同时回调

<强> 1。回调的可读性  Promise提供了一种更简洁明了的方式来表示javascript中的顺序异步操作。它们实际上是一种不同的语法,可以实现与回调相同的效果。优点是提高了可读性。像这样的东西

aAsync()   
.then(bAsync)  
 .then(cAsync)   
.done(finish); 

比将每个单独的函数作为回调传递(如

)更具可读性
Async(function(){     return bAsync(function(){         return cAsync(function(){             finish()         })     }) }); 
//-------------------------------------------- api().then(function(result){  
   return api2(); }).then(function(result2){     return api3(); }).then(function(result3){      // do work }); 

<强> 2。容易发现错误。 当然,代码不多,但更具可读性。但这不是结束。让我们发现真正的好处:如果您想检查任何步骤中的任何错误怎么办?用回调来做这件事真是太棒了,但有了承诺,这是小菜一碟:

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调用)但你需要等待三个呢?没有承诺,你应该创建某种计数器。承诺,使用ES6符号,是另一块蛋糕,非常整洁:

Promise.all([api(), api2(), api3()]).then(function(result) {     //do work. result is an array contains the values of the three fulfilled promises. }).catch(function(error) {     //handle the error. At least one of the promises rejected. });

希望你现在以新的眼光看待Promise。