承诺不仅仅是回调吗?

Ben*_*aum 402 javascript callback promise q bluebird

我已经开发了几年的JavaScript,我根本不理解有关承诺的大惊小怪.

似乎我所做的只是改变:

api(function(result){
    api2(function(result2){
        api3(function(result3){
             // do work
        });
    });
});
Run Code Online (Sandbox Code Playgroud)

无论如何,我可以使用像async这样的库,例如:

api().then(function(result){
     api2().then(function(result2){
          api3().then(function(result3){
               // do work
          });
     });
});
Run Code Online (Sandbox Code Playgroud)

哪个代码更多,可读性更低.我没有在这里获得任何东西,它也不会突然神奇地"平坦".更不用说必须将事物转换为承诺.

那么,这里的承诺有什么大惊小怪?

Osc*_*Paz 601

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

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
});
Run Code Online (Sandbox Code Playgroud)

当然,代码不多,但更具可读性.

但这不是结束.让我们发现真正的好处:如果您想检查任何步骤中的任何错误怎么办?用回调来做这件事就好了,但有了承诺,这是小菜一碟:

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
});
Run Code Online (Sandbox Code Playgroud)

几乎和try { ... } catch块一样.

更好的是:

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.
});
Run Code Online (Sandbox Code Playgroud)

更妙的是:如果这3个调用api,api2,api3可以同时运行(例如,如果他们是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.
});
Run Code Online (Sandbox Code Playgroud)

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

  • 他们真的不应该把它命名为"承诺"."未来"至少要好100倍. (113认同)
  • @Pacerier因为Future没有受到jQuery的污染? (11认同)
  • 你为什么给我贴上标签?我刚刚修改了一下语法.我不是JS专家.:) (8认同)
  • 替代模式(取决于所需的:api().然后(api2).then(api3).then(doWork);也就是说,如果api2/api3函数从最后一步获取输入,并且自己返回新的promise,它们可以只是在没有额外包装的情况下被链接.也就是说,它们构成了. (5认同)
  • 我发现这个答案是如何使用承诺的最好例子之一。所以,我不明白严重的否决?(截至今天为-6) (2认同)

Ber*_*rgi 164

是的,Promises是异步回调.他们不能做回调不能做的任何事情,并且你遇到与异步回调相同的异步问题.

然而,承诺是不仅仅是回调.它们是一个非常强大的抽象,允许更清晰,更好,功能更强的代码,更容易出错.

那么主要想法是什么?

Promise是表示单个(异步)计算结果的对象.他们只解决一次这个结果.这意味着什么:

Promise实现了一个观察者模式:

  • 您不需要知道在任务完成之前将使用该值的回调.
  • 您可以轻松地return使用Promise对象,而不是期望将回调作为函数的参数
  • promise将存储该值,您可以随时透明地添加回调.结果可用时将调用它."透明度"意味着当你有一个承诺并添加一个回调时,它对你的代码没有影响,结果是否已经到来 - API和合同是相同的,简化了缓存/记忆.
  • 您可以轻松添加多个回调

承诺是可链接的(monadic,如果你愿意):

  • 如果需要转换promise所代表的值,则将转换函数映射到promise并返回表示转换结果的新promise.您无法以某种方式同步获取值以使用它,但您可以轻松地在promise上下文中解除转换.没有样板回调.
  • 如果要链接两个异步任务,可以使用该.then()方法.它将使用第一个结果调用回调,并返回回调返回的承诺结果的承诺.

听起来很复杂?代码示例的时间.

var p1 = api1(); // returning a promise
var p3 = p1.then(function(api1Result) {
    var p2 = api2(); // returning a promise
    return p2; // The result of p2 …
}); // … becomes the result of p3

// So it does not make a difference whether you write
api1().then(function(api1Result) {
    return api2().then(console.log)
})
// or the flattened version
api1().then(function(api1Result) {
    return api2();
}).then(console.log)
Run Code Online (Sandbox Code Playgroud)

展平不是神奇的,但你可以很容易地做到.对于重度嵌套的示例,(近似)等价物将是

api1().then(api2).then(api3).then(/* do-work-callback */);
Run Code Online (Sandbox Code Playgroud)

如果看到这些方法的代码有助于理解,这里有几行最基本的承诺.

什么是承诺的大惊小怪?

Promise抽象允许更好的函数可组合性.例如,then在链接旁边,该all函数为多个并行等待的promise的组合结果创建一个promise.

最后但并非最不重要的Promise带有集成的错误处理.计算的结果可能是承诺是用值来实现的,或者是因为理由而被拒绝.所有组合函数都自动处理这个并在promise链中传播错误,因此您无需在任何地方显式地关注它 - 与普通回调实现相反.最后,您可以为所有发生的异常添加专用错误回调.

更不用说必须将事物转换为承诺.

实际上有很好的承诺库,这是非常微不足道的,请参阅如何将现有的回调API转换为承诺?

  • @hege_hegedus:有些环境中已经绑定了`console`方法.当然,我只是说两个嵌套都具有完全相同的行为,而不是它们中的任何一个都可以工作:-P (3认同)
  • 只是一点点评论:你不能使用`.then(console.log)`,因为console.log依赖于控制台上下文.这样会导致非法调用错误.使用`console.log.bind(console)`或`x => console.log(x)`来绑定上下文. (2认同)

Joh*_*isz 18

除了已经建立的答案之外,凭借ES6箭头功能,Promise从一个中等闪亮的小蓝矮星直接变成一个红色巨人.那即将崩溃成超新星:

api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))
Run Code Online (Sandbox Code Playgroud)

正如oligofren指出的那样,在api调用之间没有参数,你根本不需要匿名包装函数:

api().then(api2).then(api3).then(r3 => console.log(r3))
Run Code Online (Sandbox Code Playgroud)

最后,如果你想达到一个超大质量的黑洞水平,可以期待Promises:

async function callApis() {
    let api1Result = await api();
    let api2Result = await api2(api1Result);
    let api3Result = await api3(api2Result);

    return api3Result;
}
Run Code Online (Sandbox Code Playgroud)

  • "凭借ES6箭头功能Promise从一颗谦虚闪亮的小蓝星直接转变成一颗红巨星.即将崩溃成一颗超新星"翻译:将ES6箭头功能与Promises相结合是非常棒的:) (9认同)
  • 这使得Promises听起来像是一场宇宙灾难,我认为这不是你的意图. (3认同)

Dun*_*Luk 12

除了其他答案之外,ES2015语法与promises无缝融合,减少了更多的样板代码:

// Sequentially:
api1()
  .then(r1 => api2(r1))
  .then(r2 => api3(r2))
  .then(r3 => {
      // Done
  });

// Parallel:
Promise.all([
    api1(),
    api2(),
    api3()
]).then(([r1, r2, r3]) => {
    // Done
});
Run Code Online (Sandbox Code Playgroud)


dww*_*dww 10

除了上面的精彩答案,还可以添加2个点:

1.语义差异:

承诺可能在创建时已经解决.这意味着他们保证条件而不是事件.如果它们已经被解析,则仍然会调用传递给它的已解析函数.

相反,回调处理事件.因此,如果您感兴趣的事件发生在回调注册之前,则不会调用回调.

2.控制倒置

回调涉及控制的倒置.当您使用任何API注册回调函数时,Javascript运行时会存储回调函数,并在准备好运行后从事件循环中调用它.

请参阅Javascript事件循环以获取解释.

使用Promise,控制驻留在调用程序中.如果我们存储promise对象,则可以随时调用.then()方法.


小智 6

一点都不。

回调只是JavaScript中的函数,它们将在另一个函数执行完成后被调用并执行。那么它是如何发生的呢?

实际上,在 JavaScript 中,函数本身被视为对象,因此与所有其他对象一样,甚至函数也可以作为参数发送给其他函数。可以想到的最常见和通用的用例是 JavaScript 中的 setTimeout() 函数。

与使用回调做同样的事情相比,Promise只不过是一种更临时的处理和构造异步代码的方法。

Promise 在构造函数中接收两个回调:resolve 和 reject。Promise 中的这些回调为我们提供了对错误处理和成功案例的细粒度控制。当promise 执行成功时使用resolve 回调,reject 回调用于处理错误情况。


小智 5

Promise 不是回调,两者都是促进异步编程的编程习惯用法。使用使用协程或返回承诺的生成器的异步/等待风格的编程可以被认为是第三个这样的习惯用法。这些习语在不同编程语言(包括 Javascript)中的比较如下:https : //github.com/KjellSchubert/promise-future-task