Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。
假设现在有一个名为 createAudioFileAsync() 的函数,如果给出一些配置和两个回调函数,这个函数能异步地生成音频文件。一个回调函数是文件成功创建时的回调,另一个则是出现异常时的回调。
以下为使用 createAudioFileAsync() 的示例:
// 成功的回调函数
function successCallback(result) {
console.log("音频文件创建成功: " + result);
}
// 失败的回调函数
function failureCallback(error) {
console.log("音频文件创建失败: " + error);
}
createAudioFileAsync(audioSettings, successCallback, failureCallback)
最新的方式就是返回一个 promise 对象,使得你可以将你的回调函数绑定在该 Promise 上:
如果函数 createAudioFileAsync() 被重写为返回 Promise 对象的形式,就可以像这样简单地使用:
const promise = createAudioFileAsync(audioSettings);
promise.then(successCallback, failureCallback);
可简写为:
createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:
- 在 本轮 Javascript event loop(事件循环)运行完成 之前,回调函数是不会被调用的。
- 通过
then()添加的回调函数总会被调用,即便它是在异步操作完成之后才被添加的函数。 - 通过多次调用
then(),可以添加多个回调函数,它们会按照插入顺序一个接一个独立执行。
因此,Promise 最直接的好处就是链式调用(chaining)。
通过新的功能方法,我们把回调绑定到被返回的 Promise 上代替以往的做法,形成一个 Promise 链:
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) 的缩略形式。如下所示,我们也可以用箭头函数来表示:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。(如果使用箭头函数,() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。
Catch 的后续链式操作
有可能会在一个回调失败之后继续使用链式操作,即 使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作很有用。请阅读下面的例子:
new Promise((resolve, reject) => {
console.log('初始化');
resolve();
})
.then(() => {
throw new Error('有哪里不对了');
console.log('执行「这个」”');
})
.catch(() => {
console.log('执行「那个」');
})
.then(() => {
console.log('执行「这个」,无论前面发生了什么');
});
输出结果如下:
初始化
执行“那个”
执行“这个”,无论前面发生了什么
注意:因为抛出了错误 有哪里不对了,所以前一个 执行「这个」 没有被输出。
错误传递
在之前的回调地狱示例中,你可能记得有 3 次 failureCallback 的调用,而在 Promise 链中只有尾部的一次调用。
doSomething()
.then(result => doSomethingElse(value))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
通常,一遇到异常抛出,Promise 链就会停下来,直接调用链式中的 catch 处理程序来继续当前执行。这看起来和以下的同步代码的执行很相似。
try {
let result = syncDoSomething();
let newResult = syncDoSomethingElse(result);
let finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
在 ECMAScript 2017 标准的 async/await 语法糖中,这种同步形式代码的对称性得到了极致的体现:
async function foo() {
try {
let result = await doSomething();
let newResult = await doSomethingElse(result);
let finalResult = await doThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
}
组合
Promise.resolve() 和 Promise.reject() 是手动创建一个已经 resolve 或者 reject 的 Promise 快捷方法。它们有时很有用。
Promise.all() 和 Promise.race() 是并行运行异步操作的两个组合式工具。
我们可以发起并行操作,然后等多个操作全部结束后进行下一步操作,如下:
Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ });
可以使用一些聪明的 JavaScript 写法实现时序组合:
[func1, func2, func3].reduce((p, f) => p.then(f), Promise.resolve())
.then(result3 => { /* use result3 */ });
通常,我们递归调用一个由异步函数组成的数组时相当于一个 Promise 链:
Promise.resolve().then(func1).then(func2).then(func3);
我们也可以写成可复用的函数形式,这在函数式编程中极为普遍:
const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
composeAsync() 函数将会接受任意数量的函数作为其参数,并返回一个新的函数,该函数接受一个通过 composition pipeline 传入的初始值。这对我们来说非常有益,因为任一函数可以是异步或同步的,它们能被保证按顺序执行:
const transformData = composeAsync(func1, func2, func3);
const result3 = transformData(data);
在 ECMAScript 2017 标准中, 时序组合可以通过使用 async/await 而变得更简单:
let result;
for (const f of [func1, func2, func3]) {
result = await f(result);
}
/* use last result (i.e. result3) */

浙公网安备 33010602011771号