难道承诺只是回调吗?

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

似乎我所做的就是改变:

api(function(result){
api2(function(result2){
api3(function(result3){
// do work
});
});
});

无论如何,我可以使用异步这样的库,例如:

api().then(function(result){
api2().then(function(result2){
api3().then(function(result3){
// do work
});
});
});

代码更多,可读性更差。我在这里没有获得任何东西,它也没有突然神奇地“变平”。更不用说把事情变成承诺了。

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

124112 次浏览

承诺不是回调。promise表示异步操作的未来结果。当然,按照你的方式来写,收效甚微。但是如果你按照它们应该被使用的方式来编写它们,你可以用一种类似于同步代码的方式来编写异步代码,并且更容易遵循:

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调用),但你需要等待这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是异步回调。它们不能做回调所不能做的任何事情,并且在异步中您面临与普通回调相同的问题。

然而,promise是更多的而不仅仅是回调。它们是一种非常强大的抽象,允许更清晰、更好的功能代码和更少容易出错的样板文件。

那么主要思想是什么呢?

承诺是表示单个(异步)计算结果的对象。他们只resolve to that result一次。这意味着几件事:

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

  • 在任务完成之前,您不需要知道将使用该值的回调。
  • 你可以轻松地return一个Promise对象,而不是期望回调作为函数的参数
  • promise将存储值,并且你可以在任何你想要的时候透明的添加一个回调。当结果可用时将调用它。“透明性”意味着当你有一个承诺并向它添加一个回调时,结果是否到达对你的代码没有影响——API和契约是相同的,大大简化了缓存/记忆。
  • 您可以轻松地添加多个回调

__abc0 (__abc1, __abc2):

  • 如果你需要转换承诺所代表的值,你可以在承诺上地图一个转换函数,并获得一个表示转换后结果的新承诺。你不能以某种方式同步获取值来使用它,但是你可以很容易地在promise上下文中电梯转换。没有样板回调。
  • 如果你想链接两个异步任务,你可以使用.then()方法。它将使用第一个结果调用一个回调,并为回调返回的promise的结果返回一个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 */);

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

承诺有什么好大惊小怪的?

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

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

更不用说把事情变成承诺了。

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

承诺不是回调,两者都是促进异步编程的编程习惯。使用协程或返回承诺的生成器使用异步/等待风格的编程可以被认为是第三种这样的习惯用法。这些习语在不同编程语言(包括Javascript)中的比较如下

没有承诺只是回调的包装

< p >的例子 你可以使用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

除了已经建立的答案,用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;
}

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

除了上面这些精彩的答案,还可以再加2分:

1. 语义的差异:

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

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

2. 控制反转

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

有关解释请参考Javascript事件循环

对于承诺,控制驻留在调用程序中。.then()方法可以在任何时候被调用如果我们存储promise对象。

不,一点也不。

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

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

承诺只是一种处理和构造异步代码的临时方法,而不是用回调来做同样的事情。

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

JavaScript的Promise实际上使用回调函数来确定Promise被解析或拒绝后要做什么,因此两者并没有本质上的不同。Promises背后的主要思想是采用回调——特别是在您想要执行某种操作的地方嵌套回调,但它将更易于阅读。

承诺概述:

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

一个承诺总是有以下三种状态:

  1. 待定:每个承诺的起始状态,既未完成也未拒绝。
  2. 完成的:操作成功完成。
  3. rejected:操作失败。

挂起的承诺可以用值来解决/实现或拒绝。然后调用以下将回调作为参数的处理程序方法:

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

Promises允许程序员编写比使用回调更简单、更易读的代码。


在一个程序中,有一系列的步骤需要执行。

function f() {
step_a();
step_b();
step_c();
...
}

每一步之间通常都有信息传递。

function f() {
const a = step_a(   );
const b = step_b( a );
const c = step_c( b );
...
}

其中一些步骤可能会花费(相对)较长的时间,所以有时您希望与其他事情并行执行这些步骤。一种方法是使用线程。另一个是异步编程。(这两种方法各有利弊,这里不再讨论。)这里,我们讨论的是异步编程。

在使用异步编程时,实现上述目标的简单方法是提供一个回调函数,在步骤完成时调用它。

// step_* calls the provided function with the returned value once complete.
function f() {
step_a(
function( a )
step_b(
function( b )
step_c(
...
)
},
)
},
)
}

这很难读懂。承诺提供了一种简化代码的方法。

// step_* returns a promise.
function f() {
step_a()
.then( step_b )
.then( step_c )
...
}

返回的对象被称为promise,因为它代表了函数的未来结果(即承诺的结果)(可以是值或异常)。

尽管承诺很有帮助,但使用承诺还是有点复杂。这就是asyncawait的用武之地。在声明为async的函数中,可以使用await代替then

// step_* returns a promise.
async function f()
const a = await step_a(   );
const b = await step_b( a );
const c = await step_c( b );
...
}

不可否认,这比使用回调可读性强得多。