javascript
使用promises
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promisess
一个Promise是一个对象,代表一个异步操作最终的成功或失败。由于大部分人是已经创建的promises的消费者,这个指引将先解释如何使用返回(创建好的)promise,然后再解释如何创建。
本质上,一个promise是一个异步函数返回的对象,你可以attach回调给这个对象,而不是传递callback函数给异步函数。
想象一个函数,createAudioFileAsync(), 它接收参数是一个配置记录和一个成功的回调函数,一个失败的回调函数,然后异步生成一个声音文件。
它的实现是这样的:
function successCallback(result) {
console.log("Audio file ready at URL: " + result);
}
function failureCallback(error) {
console.error("Error generating audio file: " + error);
}
createAudioFileAsync(audioSettings, successCallback, failureCallback);
然而现代语言是返回一个promise,然后你可以attach你的callback.
createAudioFileAsync用promise重写,它就会是如下简单:
createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
它是下面的简写:
const promise = createAudioFileAsync(audioSettings);
promise.then(successCallback, failureCallback);
上面就是个异步函数调用,下面逐一介绍它的优点。
Guarantees保证
不象老式的传递回调函数,promise的下面几个保证:
-
在当前运行JavaScript事件循环的完成之前,永远不会调用回调。
-
callback通过then来添加,即使是在异步操作完成后(成功或失败),callback也保证会被调用,如前面的例子。
-
通过多次调用then来添加多个callback,每个callback会按顺序一个一个地调用。
可以使用chainning是使用promise的最大好处。
Chainning调用链
一个常用的事例是多个异步调用需要一个接一个地调用,后面的操作开始执行基于前面操作返回的结果。 这种我们可以通过promiss chain来完成。
这个魔术的原理是then返回一个新的promise,这是与原来不同的地方。
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
也可以写成
const promise2 = doSomething().then(successCallback, failureCallback);
promise2代表不仅是doSomething完成了,同样代表successCallback或failureCallback的完成,promise2可以是加外一个异步函数返回的promise. 这就是,任何添加在promise2之后的callback都会排在由successCallback
or failureCallback
返回的promise之后。
基本上,每个promise代表chain上的另外一个异步的完成。
在过去,连续执行几个异步操作会导致经典的回调金字塔:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
现代的promise chain
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
then的参数是可选的,而catch(failureCallback)
是 then(null, failureCallback)
的简写。
可以用lambda表达式.箭头函数
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
重要: 必须返回结果,否则其它callbacks获取不到前一个promise的结果。 (with arrow functions () => x
is short for () => { return x; }
).
一个具体的promise chain:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
分开调then的经典错误,下面是不会链式传递结果。
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
上面直接返回结果,下面返回的是promise
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
具体看 https://javascript.info/promise-chaining
Chaining after a catch
用catch处理chain上失败的情况
new Promise((resolve, reject) => {
console.log('Initial');//run
resolve();
})
.then(() => {
throw new Error('Something failed');
console.log('Do this');//not run
})
.catch(() => {
console.error('Do that');//run
})
.then(() => {
console.log('Do this, no matter what happened before');//run
});
输出如下:
Initial
Do that
Do this, no matter what happened before
错误传播Error propagation
你可能还记得,在之前末日金字塔(pyramid of doom)有三次失败回调,而在promise chain的末端,只有一次:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
如果链上有错误,浏览器就会往链下面 找 .catch()
or onRejected
。
Promises通过捕获所有错误,甚至抛出异常和编程错误,解决了回调金字塔的一个基本缺陷。这对于异步操作的功能组合至关重要。
Promise rejection events
当promise被 rejected时,下面两个event中的一个会发送到全局。(通常是window,或者web worker或者其它基于web worker的interface)
reject被处理后
reject未被处理
web开发者要学习下,我用nodejs跳过先。
为老的callback api封闭promise
例如旧的
setTimeout(() => saySomething("10 seconds passed"), 10*1000);
用promise
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(10*1000).then(() => saySomething("10 seconds")).catch(failureCallback);
组合Composition
Promise.all()等所有的异步操作并行执行完毕。
Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ });
顺序组合
[func1, func2, func3].reduce((p, f) => p.then(f), Promise.resolve())
.then(result3 => { /* use result3 */ });
与下面相等
Promise.resolve().then(func1).then(func2).then(func3);