首页 文章

承诺不仅仅是回调吗?

提问于
浏览
361

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

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

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

7 回答

  • 149

    承诺不是回调 . 承诺代表 future result of an asynchronous operation . 当然,按照你的方式写它们,你得到的好处不大 . 但是如果按照它们的使用方式编写它们,您可以以类似于同步代码的方式编写异步代码,并且更容易遵循:

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

    甚至更好:如果那些对 apiapi2 ,_568120的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 .

  • 6

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

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

    那么主要想法是什么?

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

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

    • 在任务完成之前,您不需要知道将使用该值的回调 .

    • 而不是期望回调作为函数的参数,你可以轻松 return 一个Promise对象

    • promise将存储该值,您可以随时透明地添加回调 . 结果可用时将调用它 . "Transparency"意味着当你有一个承诺并添加一个回调时,它对你的代码没有影响,无论结果是否已经到来 - API和 Contract 是相同的,简化了缓存/备忘 .

    • 您可以轻松添加多个回调

    Promises are chainable(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)
    

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

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

    如果看到这些方法的代码有助于理解,here's a most basic promise lib in a few lines .

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

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

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

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

    实际上有很好的承诺库,这是非常微不足道的,请参阅How do I convert an existing callback API to promises?

  • 544

    除了已经 Build 的答案之外,凭借ES6箭头功能,Promise从一颗谦虚闪亮的小蓝矮星直接转变为红色巨人 . 那即将崩溃成超新星:

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

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

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

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

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

    除了其他答案之外,ES2015语法与承诺无缝融合,进一步降低样板代码:

    // Sequentially:
    api1()
      .then(r1 => api2(r1))
      .then(r2 => api3(r2))
      .then(r3 => {
          // Done
      });
    
    // Parallel:
    Promise.all([
        api1(),
        api2(),
        api3()
    ]).then(([r1, r2, r3]) => {
        // Done
    });
    
  • 17

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

    1. Semantic difference:

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

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

    2. Inversion of control

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

    有关说明,请参阅The Javascript Event loop .

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

  • 4

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

  • 10

    没有承诺只是回调的包装

    示例您可以将javascript本机承诺与节点js一起使用

    my cloud 9 code link : https://ide.c9.io/adx2803/native-promises-in-node
    
    /**
    * Created by dixit-lab on 20/6/16.
    */
    
    var express = require('express');
    var request = require('request');   //Simplified HTTP request client.
    
    
    var app = express();
    
    function promisify(url) {
        return new Promise(function (resolve, reject) {
        request.get(url, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            resolve(body);
        }
        else {
            reject(error);
        }
        })
        });
    }
    
    //get all the albums of a user who have posted post 100
    app.get('/listAlbums', function (req, res) {
    //get the post with post id 100
    promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
    var obj = JSON.parse(result);
    return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
    })
    .catch(function (e) {
        console.log(e);
    })
    .then(function (result) {
        res.end(result);
    }
    )
    
    })
    
    
    var server = app.listen(8081, function () {
    
    var host = server.address().address
    var port = server.address().port
    
    console.log("Example app listening at http://%s:%s", host, port)
    
    })
    
    
    //run webservice on browser : http://localhost:8081/listAlbums
    

相关问题