485

我已经开发 JavaScript 几年了,我完全不理解关于 Promise 的大惊小怪。

似乎我所做的只是改变:

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

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

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

4

10 回答 10

691

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

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

甚至更好:如果对api, api2,的这 3 个调用api3可以同时运行(例如,如果它们是 AJAX 调用)但您需要等待这三个调用会怎样?如果没有承诺,您应该必须创建某种计数器。有了 Promise,使用 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。

于 2014-03-20T17:07:54.880 回答
190

是的,Promises 是异步回调。他们不能做任何回调不能做的事情,而且你在异步和普通回调中面临同样的问题。

然而,Promises不仅仅是回调。它们是一个非常强大的抽象,允许使用更简洁、更好的功能代码和不易出错的样板。

那么主要思想是什么?

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

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

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

Promise是可链接的(如果你愿意,可以使用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 */);

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

承诺有什么大惊小怪的?

Promise 抽象允许更好的函数组合性。例如,在thenfor chaining 旁边,该all函数为多个并行等待的 Promise 的组合结果创建一个 Promise。

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

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

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

于 2014-03-21T14:50:23.067 回答
25

除了已经确定的答案之外,使用 ES6 箭头函数,Promises 会从一颗适度闪亮的小蓝矮星直接变成一颗红巨星。那即将坍缩成超新星:

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;
}
于 2016-09-12T20:25:22.437 回答
21

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

1.语义差异:

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

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

2.控制反转

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

有关说明,请参阅Javascript 事件循环

使用Promises,控制权位于调用程序。如果我们存储了 Promise 对象,则可以随时调用 .then() 方法。

于 2018-04-18T08:28:33.510 回答
15

除了其他答案之外,ES2015 语法与 Promise 无缝融合,减少了更多样板代码:

// Sequentially:
api1()
  .then(r1 => api2(r1))
  .then(r2 => api3(r2))
  .then(r3 => {
      // Done
  });

// Parallel:
Promise.all([
    api1(),
    api2(),
    api3()
]).then(([r1, r2, r3]) => {
    // Done
});
于 2017-07-23T13:24:34.183 回答
6

一点都不。

回调只是JavaScript中的函数,在另一个函数执行完成后将被调用并执行。那么它是怎么发生的呢?

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

与使用回调做同样的事情相比, Promise只不过是一种更即兴的处理和构建异步代码的方法。

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

于 2019-03-06T07:40:04.710 回答
5

Promise 不是回调,两者都是促进异步编程的编程习惯。使用返回承诺的协程或生成器使用异步/等待风格的编程可以被认为是第三个这样的习惯用法。不同编程语言(包括 Javascript)的这些习语的比较在这里:https ://github.com/KjellSchubert/promise-future-task

于 2014-04-01T14:09:29.043 回答
3

没有承诺只是回调的包装

示例您可以将 javascript 原生 Promise 与 node 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
于 2016-06-20T13:42:01.603 回答
1

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

于 2019-12-20T07:49:05.590 回答
0

承诺概述:

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

Promise 总是具有以下 3 种状态:

  1. 待定:每个承诺的开始状态,既不履行也不拒绝。
  2. 已完成:操作成功完成。
  3. 拒绝:操作失败。

一个未决的承诺可以用一个值来解决/完成或拒绝。然后调用以下将回调作为参数的处理程序方法:

  1. Promise.prototype.then(): 当 promise 被解决时,这个函数的回调参数将被调用。
  2. Promise.prototype.catch(): 当 promise 被拒绝时,这个函数的回调参数将被调用。

尽管上述方法技能获取回调参数,但它们比仅使用回调要好得多,这里有一个例子可以说明很多:

例子

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 解决或拒绝
  • 如果 promise 被解析,第一个then方法被调用,解析的值作为回调的参数传入
  • 如果 promise 被拒绝,catch则调用第一个方法并将拒绝的值作为参数传入
  • catchand方法返回承诺,这then就是我们可以链接它们的原因。Promise.resolve它们将任何返回值和任何抛出的值(使用throw关键字)包装在Promise.reject. 所以任何返回的值都会被转换成一个 Promise,在这个 Promise 上我们可以再次调用一个处理函数。
  • 与嵌套回调相比,Promise 链为我们提供了更精细的控制和更好的概览。例如,该catch方法处理在处理程序之前发生的所有错误catch
于 2020-05-06T09:03:41.970 回答