我已经开发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.
});
更好的是:如果对api
,api2
,api3
的这3个调用可以同时运行(例如,如果它们是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对象承诺是可链式的(如果你想要的话,是单元式的):
.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 lib。
承诺有什么大惊小怪的?
Promise抽象允许更好的函数可组合性。例如,在用于链接的then
旁边,all
函数为多个并行等待承诺的组合结果创建一个承诺。
最后但并非最不重要的承诺是集成错误处理。计算的结果可能是,要么用一个值实现了承诺,要么用一个理由拒绝了承诺。所有组合函数都会自动处理这个问题,并在promise链中传播错误,因此您不需要在任何地方显式地关心它--与普通回调实现不同。最后,您可以为所有发生的异常添加一个专用的错误回调。
更别提要把事情变成承诺了。
对于好的promise库,这是非常微不足道的,请看如何将现有的回调API转换为Promises?
除了已经确定的答案,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;
}