不承诺只是回调?

我几年来一直在开发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
          });
     });
});

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

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


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

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次调用apiapi2api3可以同时运行(例如,如果它们是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是异步回调。 他们不能做任何回调无法做到的事情,而且你会遇到与普通回调相同的异步问题。

然而,Promise不仅仅是回调。 它们是一个非常强大的抽象,允许更干净,更好,功能强大的代码,而且容易出错的样板文件。

那么主要想法是什么?

Promise是代表单个(异步)计算结果的对象。 他们只解决了这个结果一次。 这意味着一些事情:

承诺实施观察员模式:

  • 您不需要知道在任务完成之前将使用该值的回调。
  • 而不是期待回调作为你的函数的参数,你可以轻松地return一个Promise对象
  • 承诺将存储该值,并且您可以随时透明地添加回调。 当结果可用时它将被调用。 “透明度”意味着当你有承诺并为其添加回调时,不管结果是否已经到达,它对你的代码都没有什么影响 - API和契约是一样的,简化了缓存/记忆。
  • 您可以轻松添加多个回调
  • 承诺是可链接的(如果你愿意,可以是单一的):

  • 如果您需要转换承诺所代表的价值,您可以将转换函数映射到承诺上并获得表示转换结果的新承诺。 您无法以某种方式同步获取该值,但可以轻松解除承诺上下文中的转换。 没有样板回调。
  • 如果你想链接两个异步任务,你可以使用.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 */);
    

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

    什么是承诺的大惊小怪?

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

    最后但并非最不重要的承诺带有集成的错误处理。 计算的结果可能是承诺是用价值来实现的,或者是有理由拒绝的。 所有的组合函数都会自动处理这个问题,并在promise链中传播错误,所以你不需要在任何地方明确地关心它 - 与简单的回调实现相反。 最后,您可以为所有发生的异常添加专用错误回调。

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

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


    除了Oscar和Bergi的真棒答案外,ES6的箭头功能Promises还是从一颗温和的小蓝星变成了一个红巨星。 即将崩溃成超新星:

    api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))
    
    链接地址: http://www.djcxy.com/p/1625.html

    上一篇: Aren't promises just callbacks?

    下一篇: How synchronous AJAX call could cause memory leak?