承诺不就是回调吗?

2022-08-29 23:07:38

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

似乎我所做的只是改变:

api(function(result){
    api2(function(result2){
        api3(function(result3){
             // do work
        });
    });
});

无论如何,我都可以使用像async这样的库,如下所示:

api().then(function(result){
     api2().then(function(result2){
          api3().then(function(result3){
               // do work
          });
     });
});

这是更多的代码和更少的可读性。我在这里没有得到任何东西,它也没有突然神奇地“平坦”。更不用说必须将事情转化为承诺。

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


答案 1

承诺不是回调。承诺表示异步操作的未来结果。当然,按照你的方式写它们,你得到的好处很少。但是,如果您按照预期的方式编写异步代码,则可以以类似于同步代码且更易于遵循的方式编写异步代码:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
});

当然,不是更少的代码,而是更具可读性。

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

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
});

几乎与块相同。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.
});

更好的是:如果对 、 的 3 个调用可以同时运行(例如,如果它们是 AJAX 调用),但您需要等待这三个调用,该怎么办?如果没有承诺,你应该必须创建某种计数器。有了承诺,使用ES6符号,是另一块蛋糕,非常整洁:apiapi2api3

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.
});

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


答案 2

是的,承诺是异步回调。它们无法执行回调无法执行的任何操作,并且您面临着与普通回调相同的异步问题。

但是,承诺不仅仅是回调。它们是一个非常强大的抽象,允许更干净,更好的功能代码,并且不容易出错的样板。

那么主要思想是什么呢?

承诺是表示单个(异步)计算结果的对象。他们只解决一次这个结果。这意味着以下几点:

承诺实现观察者模式:

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

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

  • 如果需要转换 promise 所表示的值,请将转换函数映射到 promise 上,并取回表示转换后结果的新 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)

扁平化不会神奇地出现,但你可以很容易地做到这一点。对于重嵌套的示例,(近)等效项将为

api1().then(api2).then(api3).then(/* do-work-callback */);

如果查看这些方法的代码有助于理解,那么下面几行就是最基本的承诺库

关于承诺有什么大惊小怪的?

Promise抽象允许函数具有更好的可组合性。例如,在 用于链接的 旁边,该函数为多个并行等待承诺的组合结果创建一个承诺。thenall

最后但并非最不重要的一点是,Promises带有集成的错误处理功能。计算的结果可能是,要么承诺以值实现,要么因原因被拒绝。所有组合函数都会自动处理此情况并在承诺链中传播错误,因此您无需在任何地方都显式关注它 - 与普通回调实现相反。最后,您可以为所有发生的异常添加专用的错误回调。

更不用说必须将事情转化为承诺。

这对于良好的 promise 库来说其实是微不足道的,请参阅如何将现有的回调 API 转换为 promise?