Aren承诺只是回调?

时间:2014-03-20 16:49:08

标签: javascript callback promise q bluebird

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

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

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

10 个答案:

答案 0 :(得分:568)

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

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调用),但是您需要等待这三次调用,该怎么办?没有承诺,你应该创建某种计数器。承诺,使用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。

答案 1 :(得分:155)

是的,Promises是异步回调。他们无法做任何回调无法做到的事情,并且你会遇到与异步回调相同的异步问题。

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

  

那么主要想法是什么?

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

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

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

Promises are chainable monadic ,如果你愿意的话):

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

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

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链中传播错误,这样你就不需要在任何地方显式地关注它 - 与普通回调实现相反。最后,您可以为所有发生的异常添加专用的错误回调。

  

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

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

答案 2 :(得分:19)

除了已经确定的答案之外,还有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))

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

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

    return api3Result;
}

答案 3 :(得分:10)

除了其他答案之外,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
});

答案 4 :(得分:7)

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

<强> 1。语义差异:

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

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

<强> 2。控制倒置

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

请参阅The Javascript Event loop以获取解释。

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

答案 5 :(得分:4)

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

答案 6 :(得分:1)

没有承诺只是回调的包装

示例     您可以将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

答案 7 :(得分:0)

不,完全没有。

Callbacks只是JavaScript中的函数,在另一个函数的执行完成之后,这些函数将被调用然后执行。那怎么发生的呢?

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

Promises仅仅是一种处理和构造异步代码的简易方法,与通过回调进行处理相比,它更为简易。

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

答案 8 :(得分:0)

JavaScript Promises实际上使用回调函数来确定在Promise被解决或拒绝后该怎么做,因此两者没有本质上的不同。 Promises的主要思想是进行回调-尤其是嵌套的回调,您想在其中执行某种操作,但是它更具可读性。

答案 9 :(得分:0)

承诺概述:

在JS中,我们可以在promise中包装异步操作(例如数据库调用,AJAX调用)。通常,我们要对检索到的数据运行一些其他逻辑。 JS Promise具有处理程序功能,用于处理异步操作的结果。处理函数甚至可以在其中具有其他异步操作,这些操作可能依赖于先前异步操作的值。

承诺始终具有以下3种状态:

  1. 待处理:每个诺言的开始状态,既未实现也未拒绝。
  2. 已完成:操作成功完成。
  3. 拒绝:操作失败。

未解决的承诺可以被解决/被履行或被拒绝。然后调用以下将回调作为参数的处理程序方法:

  1. Promise.prototype.then():兑现承诺后,将调用此函数的回调参数。
  2. Promise.prototype.catch():当承诺被拒绝时,将调用此函数的回调参数。

尽管上述方法技巧获得了回调参数,但它们远比使用 仅回调这里是一个示例,将阐明很多内容:

示例

function createProm(resolveVal, rejectVal) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                console.log("Resolved");
                resolve(resolveVal);
            } else {
                console.log("Rejected");
                reject(rejectVal);
            }
        }, 1000);
    });
}

createProm(1, 2)
    .then((resVal) => {
        console.log(resVal);
        return resVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
        return resVal + 2;
    })
    .catch((rejectVal) => {
        console.log(rejectVal);
        return rejectVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
    })
    .finally(() => {
        console.log("Promise done");
    });

  • createProm函数创建一个承诺,该承诺将在1秒后根据随机Nr来解决或拒绝
  • 如果已解决了承诺,则会调用第一个then方法,并将已解决的值作为回调的参数传入
  • 如果承诺被拒绝,则会调用第一个catch方法,并将拒绝的值作为参数传递
  • catchthen方法返回promise,这就是我们可以链接它们的原因。他们将所有返回的值包装在Promise.resolve中,并将所有抛出的值(使用throw关键字包装在Promise.reject中。因此,任何返回的值都将转换为一个承诺,并且在这个承诺上,我们可以再次调用处理程序函数。
  • 与嵌套回调相比,Promise链为我们提供了更好的控制和更好的概览。例如,catch方法处理所有在catch处理程序之前发生的错误。