提问者:小点点

promise不就是回调吗?


我已经开发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
          });
     });
});

这样代码更多,可读性更差。我在这里什么也没收获,也不是突然间神奇地“变平”了。更别提要把事情变成承诺了。

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


共3个答案

匿名用户

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

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

更好的是:如果对apiapi2api3的这3个调用可以同时运行(例如,如果它们是AJAX调用),但您需要等待这3个调用,该怎么办?没有承诺,你应该创建某种计数器。使用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对象
  • ,而不是期望回调作为函数的参数
  • 承诺将存储值,您可以随时透明地添加回调。当结果可用时将调用它。“透明性”意味着,当您有一个承诺并向其添加回调时,结果是否已经到达对您的代码没有影响--API和契约是相同的,大大简化了缓存/记忆。
  • 您可以轻松添加多个回调

承诺是可链式的(如果你想要的话,是单元式的):

  • 如果您需要转换承诺所表示的值,可以在承诺上映射一个转换函数,并获取一个表示转换结果的新承诺。您无法以某种方式同步获取值来使用它,但您可以轻松地在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 lib。

承诺有什么大惊小怪的?

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

最后但并非最不重要的承诺是集成错误处理。计算的结果可能是,要么用一个值实现了承诺,要么用一个理由拒绝了承诺。所有组合函数都会自动处理这个问题,并在promise链中传播错误,因此您不需要在任何地方显式地关心它--与普通回调实现不同。最后,您可以为所有发生的异常添加一个专用的错误回调。

更别提要把事情变成承诺了。

对于好的promise库,这是非常微不足道的,请看如何将现有的回调API转换为Promises?

匿名用户

除了已经确定的答案,ES6箭头功能承诺从一个谦虚闪亮的蓝色小矮人直接变成一个红巨星。即将坍缩成超新星:

api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))

正如oligofren指出的,如果api调用之间没有参数,您根本不需要匿名包装器函数:

api().then(api2).then(api3).then(r3 => console.log(r3))

最后,如果你想要达到超大质量黑洞的水平,你可以等待许诺:

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

    return api3Result;
}