Promise.resolve()
:创建一个以给定值完成的 PromisePromise.reject()
:创建一个以给定值拒绝的 Promise.then()
回调中返回和抛出.catch()
及其回调.finally()
[ES2018]XMLHttpRequest
util.promisify()
Promise.all()
Promise.race()
Promise.any()
和 AggregateError
[ES2021]Promise.allSettled()
[ES2020]Promise.all()
(高级)Promise.all()
是 fork-joinPromise.all()
Promise.race()
Promise.any()
[ES2021]Promise.allSettled()
[ES2020]推荐阅读
本章建立在前一章的基础上,介绍了 JavaScript 中的异步编程。
Promise 是一种用于异步传递结果的技术。
以下代码是使用基于 Promise 的函数 addAsync()
的示例(其实现将在稍后展示)
addAsync(3, 4)
.then(result => { // success
.equal(result, 7);
assert
}).catch(error => { // failure
.fail(error);
assert; })
Promise 类似于事件模式:有一个对象(一个 *Promise*),我们在其中注册回调
.then()
注册处理结果的回调。.catch()
注册处理错误的回调。基于 Promise 的函数返回一个 Promise,并在完成时(如果完成)向其发送结果或错误。Promise 将其传递给相关的回调。
与事件模式相比,Promise 针对一次性结果进行了优化
.then()
和 .catch()
,因为它们都返回 Promise。这有助于顺序调用多个异步函数。稍后将详细介绍。什么是 Promise?有两种看待它的方式
这是一个基于 Promise 的函数的实现,它将两个数字 x
和 y
相加
function addAsync(x, y) {
return new Promise(
, reject) => { // (A)
(resolveif (x === undefined || y === undefined) {
reject(new Error('Must provide two parameters'));
else {
} resolve(x + y);
};
}) }
addAsync()
立即调用 Promise
构造函数。该函数的实际实现位于传递给该构造函数的回调中(A 行)。该回调提供了两个函数
resolve
用于传递结果(成功的情况下)。reject
用于传递错误(失败的情况下)。图 22 描述了 Promise 可以处于的三种状态。Promise 专注于一次性结果,并保护我们免受 *竞争条件*(注册过早或过晚)的影响
.then()
回调或 .catch()
回调,则一旦 Promise 完成,就会通知它。.then()
或 .catch()
,它们将收到缓存的值。此外,一旦 Promise 完成,其状态和完成值就不能再更改。这有助于使代码可预测并强制执行 Promise 的一次性性质。
有些 Promise 永远不会完成
Promise 可能永远不会完成。例如
new Promise(() => {})
Promise.resolve()
:创建一个以给定值完成的 PromisePromise.resolve(x)
创建一个以值 x
完成的 Promise
Promise.resolve(123)
.then(x => {
.equal(x, 123);
assert; })
如果参数已经是一个 Promise,则按原样返回
const abcPromise = Promise.resolve('abc');
.equal(
assertPromise.resolve(abcPromise),
; abcPromise)
因此,给定一个任意值 x
,我们可以使用 Promise.resolve(x)
来确保我们有一个 Promise。
请注意,名称是 resolve
,而不是 fulfill
,因为如果 .resolve()
的参数是一个被拒绝的 Promise,它将返回一个被拒绝的 Promise。
Promise.reject()
:创建一个以给定值拒绝的 PromisePromise.reject(err)
创建一个以值 err
拒绝的 Promise
const myError = new Error('My error!');
Promise.reject(myError)
.catch(err => {
.equal(err, myError);
assert; })
.then()
回调中返回和抛出.then()
处理 Promise 的完成。它还返回一个新的 Promise。该 Promise 如何完成取决于回调内部发生的情况。让我们来看看三种常见情况。
首先,回调可以返回一个非 Promise 值(A 行)。因此,.then()
返回的 Promise 将以该值完成(如 B 行中所检查)
Promise.resolve('abc')
.then(str => {
return str + str; // (A)
}).then(str2 => {
.equal(str2, 'abcabc'); // (B)
assert; })
其次,回调可以返回一个 Promise p
(A 行)。因此,p
“成为”.then()
返回的内容。换句话说:.then()
已经返回的 Promise 被有效地替换为 p
。
Promise.resolve('abc')
.then(str => {
return Promise.resolve(123); // (A)
}).then(num => {
.equal(num, 123);
assert; })
为什么这很有用?我们可以返回基于 Promise 的操作的结果,并通过“扁平的”(非嵌套的).then()
处理其完成值。比较
// Flat
asyncFunc1()
.then(result1 => {
/*···*/
return asyncFunc2();
}).then(result2 => {
/*···*/
;
})
// Nested
asyncFunc1()
.then(result1 => {
/*···*/
asyncFunc2()
.then(result2 => {
/*···*/
;
}); })
第三,回调可以抛出异常。因此,.then()
返回的 Promise 将以该异常被拒绝。也就是说,同步错误被转换为异步错误。
const myError = new Error('My error!');
Promise.resolve('abc')
.then(str => {
throw myError;
}).catch(err => {
.equal(err, myError);
assert; })
.catch()
及其回调.then()
和 .catch()
之间的区别在于,后者由拒绝触发,而不是由完成触发。但是,这两种方法都以相同的方式将回调的操作转换为 Promise。例如,在以下代码中,A 行中 .catch()
回调返回的值成为完成值
const err = new Error();
Promise.reject(err)
.catch(e => {
.equal(e, err);
assert// Something went wrong, use a default value
return 'default value'; // (A)
}).then(str => {
.equal(str, 'default value');
assert; })
.then()
和 .catch()
始终返回 Promise。这使我们能够创建任意长的方法调用链
function myAsyncFunc() {
return asyncFunc1() // (A)
.then(result1 => {
// ···
return asyncFunc2(); // a Promise
}).then(result2 => {
// ···
return result2 ?? '(Empty)'; // not a Promise
}).then(result3 => {
// ···
return asyncFunc4(); // a Promise
;
}) }
由于链接,A 行中的 return
返回最后一个 .then()
的结果。
在某种程度上,.then()
是同步分号的异步版本
.then()
顺序执行两个异步操作。我们还可以将 .catch()
添加到其中,并让它同时处理多个错误源
asyncFunc1()
.then(result1 => {
// ···
return asyncFunction2();
}).then(result2 => {
// ···
}).catch(error => {
// Failure: handle errors of asyncFunc1(), asyncFunc2()
// and any (sync) exceptions thrown in previous callbacks
; })
.finally()
[ES2018]Promise 方法 .finally()
通常按如下方式使用
somePromise.then((result) => {
// ···
}).catch((error) => {
// ···
}).finally(() => {
// ···
});
.finally()
回调始终执行,而与 somePromise
以及 .then()
和/或 .catch()
返回的值无关。相反
somePromise
完成时,才会执行 .then()
回调。.catch()
回调somePromise
被拒绝,.then()
回调返回一个被拒绝的 Promise,.then()
回调抛出异常。.finally()
忽略其回调返回的内容,并简单地传递调用它之前存在的完成状态
Promise.resolve(123)
.finally(() => {})
.then((result) => {
.equal(result, 123);
assert;
})
Promise.reject('error')
.finally(() => {})
.catch((error) => {
.equal(error, 'error');
assert; })
但是,如果 .finally()
回调抛出异常,则 .finally()
返回的 Promise 将被拒绝
Promise.reject('error (originally)')
.finally(() => {
throw 'error (finally)';
}).catch((error) => {
.equal(error, 'error (finally)');
assert; })
.finally()
的用例:清理.finally()
的一个常见用例类似于同步 finally
子句的常见用例:在使用完资源后进行清理。无论一切顺利还是出现错误,都应该始终这样做,例如
let connection;
.open()
db.then((conn) => {
= conn;
connection return connection.select({ name: 'Jane' });
}).then((result) => {
// Process result
// Use `connection` to make more queries
})// ···
.catch((error) => {
// handle errors
}).finally(() => {
.close();
connection; })
.finally()
的用例:在任何类型的完成之后首先执行某些操作我们还可以在 .then()
和 .catch()
之前使用 .finally()
。然后,我们在 .finally()
回调中执行的操作始终在其他两个回调之前执行。
例如,这就是完成的 Promise 会发生的情况
Promise.resolve('fulfilled')
.finally(() => {
console.log('finally');
}).then((result) => {
console.log('then ' + result);
}).catch((error) => {
console.log('catch ' + error);
});
// Output:
// 'finally'
// 'then fulfilled'
这就是被拒绝的 Promise 会发生的情况
Promise.reject('rejected')
.finally(() => {
console.log('finally');
}).then((result) => {
console.log('then ' + result);
}).catch((error) => {
console.log('catch ' + error);
});
// Output:
// 'finally'
// 'catch rejected'
在处理一次性结果时,Promise 相对于普通回调具有一些优势
基于 Promise 的函数和方法的类型签名更清晰:如果函数是基于回调的,则某些参数与输入有关,而末尾的一个或两个回调与输出有关。使用 Promise,所有与输出相关的内容都通过返回值处理。
链接异步处理步骤更方便。
Promise 可以处理异步错误(通过拒绝)和同步错误:在 new Promise()
、.then()
和 .catch()
的回调内部,异常会被转换为拒绝。相反,如果我们对异步使用回调,则通常不会为我们处理异常;我们必须自己处理。
Promise 是一个单一标准,正在慢慢取代几种互不兼容的替代方案。例如,在 Node.js 中,许多函数现在都提供了基于 Promise 的版本。新的异步浏览器 API 通常都是基于 Promise 的。
Promise 的最大优势之一是不需要直接使用它们:它们是 *async 函数* 的基础,async 函数是一种用于执行异步计算的同步外观语法。异步函数将在下一章中介绍。
查看 Promise 的实际应用有助于理解它们。让我们看一些例子。
考虑以下文本文件 person.json
,其中包含 JSON 数据
{
"first": "Jane",
"last": "Doe"
}
让我们来看看两个版本的代码,它们读取这个文件并将其解析成一个对象。首先,基于回调的版本。其次,基于 Promise 的版本。
以下代码读取该文件的内容并将其转换为 JavaScript 对象。它基于 Node.js 风格的回调
import * as fs from 'fs';
.readFile('person.json',
fs, text) => {
(errorif (error) { // (A)
// Failure
.fail(error);
assertelse {
} // Success
try { // (B)
const obj = JSON.parse(text); // (C)
.deepEqual(obj, {
assertfirst: 'Jane',
last: 'Doe',
;
})catch (e) {
} // Invalid JSON
.fail(e);
assert
}
}; })
fs
是一个内置的 Node.js 模块,用于文件系统操作。我们使用基于回调的函数 fs.readFile()
来读取名为 person.json
的文件。如果我们成功了,内容将通过参数 text
作为字符串传递。在 C 行,我们将该字符串从基于文本的数据格式 JSON 转换为 JavaScript 对象。JSON
是一个具有用于消费和生成 JSON 的方法的对象。它是 JavaScript 标准库的一部分,并在 本书后面 有详细介绍。
请注意,这里有两种错误处理机制:A 行中的 if
处理 fs.readFile()
报告的异步错误,而 B 行中的 try
处理 JSON.parse()
报告的同步错误。
以下代码使用 readFileAsync()
,它是 fs.readFile()
的基于 Promise 的版本(通过 util.promisify()
创建,稍后解释)
readFileAsync('person.json')
.then(text => { // (A)
// Success
const obj = JSON.parse(text);
.deepEqual(obj, {
assertfirst: 'Jane',
last: 'Doe',
;
})
}).catch(err => { // (B)
// Failure: file I/O error or JSON syntax error
.fail(err);
assert; })
函数 readFileAsync()
返回一个 Promise。在 A 行,我们通过该 Promise 的 .then()
方法指定了一个成功回调。then
回调中的其余代码是同步的。
.then()
返回一个 Promise,它允许在 B 行调用 Promise 方法 .catch()
。我们使用它来指定一个失败回调。
请注意,.catch()
允许我们同时处理 readFileAsync()
的异步错误和 JSON.parse()
的同步错误,因为 .then()
回调内部的异常会变成拒绝。
XMLHttpRequest
我们之前已经看到了用于在 Web 浏览器中下载数据的基于事件的 XMLHttpRequest
API。以下函数对该 API 进行了 Promise 化
function httpGet(url) {
return new Promise(
, reject) => {
(resolveconst xhr = new XMLHttpRequest();
.onload = () => {
xhrif (xhr.status === 200) {
resolve(xhr.responseText); // (A)
else {
} // Something went wrong (404, etc.)
reject(new Error(xhr.statusText)); // (B)
}
}.onerror = () => {
xhrreject(new Error('Network error')); // (C)
;
}.open('GET', url);
xhr.send();
xhr;
}) }
请注意 XMLHttpRequest
的结果和错误是如何通过 resolve()
和 reject()
处理的
以下是使用 httpGet()
的方法
httpGet('http://example.com/textfile.txt')
.then(content => {
.equal(content, 'Content of textfile.txt\n');
assert
}).catch(error => {
.fail(error);
assert; })
练习:Promise 超时
exercises/promises/promise_timeout_test.mjs
util.promisify()
util.promisify()
是一个实用函数,它将基于回调的函数 f
转换为基于 Promise 的函数。也就是说,我们正在从这种类型签名
f(arg_1, ···, arg_n, (err: Error, result: T) => void) : void
转换为这种类型签名
f(arg_1, ···, arg_n) : Promise<T>
以下代码对基于回调的 fs.readFile()
进行了 Promise 化(A 行)并使用它
import * as fs from 'fs';
import {promisify} from 'util';
const readFileAsync = promisify(fs.readFile); // (A)
readFileAsync('some-file.txt', {encoding: 'utf8'})
.then(text => {
.equal(text, 'The content of some-file.txt\n');
assert
}).catch(err => {
.fail(err);
assert; })
练习:util.promisify()
util.promisify()
:exercises/promises/read_file_async_exrc.mjs
util.promisify()
:exercises/promises/my_promisify_test.mjs
所有现代浏览器都支持 Fetch,这是一种新的基于 Promise 的 API,用于下载数据。可以将其视为 XMLHttpRequest
的基于 Promise 的版本。以下是 该 API 的摘录
interface Body {
text() : Promise<string>;
···
}interface Response extends Body {
···
}declare function fetch(str) : Promise<Response>;
这意味着我们可以像这样使用 fetch()
fetch('http://example.com/textfile.txt')
.then(response => response.text())
.then(text => {
.equal(text, 'Content of textfile.txt\n');
assert; })
练习:使用 fetch API
exercises/promises/fetch_json_test.mjs
实现函数和方法的规则
不要混淆(异步)拒绝和(同步)异常。
这使得我们的同步和异步代码更具可预测性和更简单,因为我们始终可以专注于单一错误处理机制。
对于基于 Promise 的函数和方法,该规则意味着它们永远不应该抛出异常。唉,很容易不小心就弄错了——例如
// Don’t do this
function asyncFunc() {
doSomethingSync(); // (A)
return doSomethingAsync()
.then(result => {
// ···
;
}) }
问题是,如果在 A 行抛出异常,那么 asyncFunc()
将抛出一个异常。该函数的调用者只期望拒绝,而没有为异常做好准备。我们可以通过三种方法来解决这个问题。
我们可以将函数的整个主体包装在一个 try-catch
语句中,并在抛出异常时返回一个被拒绝的 Promise
// Solution 1
function asyncFunc() {
try {
doSomethingSync();
return doSomethingAsync()
.then(result => {
// ···
;
})catch (err) {
} return Promise.reject(err);
} }
鉴于 .then()
会将异常转换为拒绝,我们可以在 .then()
回调内部执行 doSomethingSync()
。为此,我们通过 Promise.resolve()
启动一个 Promise 链。我们忽略了初始 Promise 的完成值 undefined
。
// Solution 2
function asyncFunc() {
return Promise.resolve()
.then(() => {
doSomethingSync();
return doSomethingAsync();
}).then(result => {
// ···
;
}) }
最后,new Promise()
也会将异常转换为拒绝。因此,使用此构造函数类似于前面的解决方案
// Solution 3
function asyncFunc() {
return new Promise((resolve, reject) => {
doSomethingSync();
resolve(doSomethingAsync());
}).then(result => {
// ···
;
}) }
大多数基于 Promise 的函数都按如下方式执行
以下代码演示了这一点
function asyncFunc() {
console.log('asyncFunc');
return new Promise(
, _reject) => {
(resolveconsole.log('new Promise()');
resolve();
;
})
}console.log('START');
asyncFunc()
.then(() => {
console.log('.then()'); // (A)
;
})console.log('END');
// Output:
// 'START'
// 'asyncFunc'
// 'new Promise()'
// 'END'
// '.then()'
我们可以看到,new Promise()
的回调在代码结束之前执行,而结果在稍后传递(A 行)。
这种方法的好处
同步开始有助于避免竞争条件,因为我们可以依赖于基于 Promise 的函数开始的顺序。在 下一章 中有一个例子,其中文本被写入文件并避免了竞争条件。
链接 Promise 不会使其他任务的处理时间不足,因为在 Promise 结束之前,总会有一个中断,在此期间事件循环可以运行。
基于 Promise 的函数总是异步返回结果;我们可以确定永远不会有同步返回。这种可预测性使代码更容易处理。
有关此方法的更多信息
“为异步设计 API”,作者:Isaac Z. Schlueter
组合器模式 是函数式编程中用于构建结构的一种模式。它基于两种函数
当涉及到 JavaScript Promise 时
基本函数包括:Promise.resolve()
、Promise.reject()
组合器包括:Promise.all()
、Promise.race()
、Promise.any()
、Promise.allSettled()
。在每种情况下
接下来,我们将仔细研究一下提到的 Promise 组合器。
Promise.all()
这是 Promise.all()
的类型签名
Promise.all<T>(promises: Iterable<Promise<T>>): Promise<Array<T>>
Promise.all()
返回一个 Promise,它
promises
都已完成,则完成。promises
的完成值的数组。这是一个输出 Promise 被完成的快速演示
const promises = [
Promise.resolve('result a'),
Promise.resolve('result b'),
Promise.resolve('result c'),
;
]Promise.all(promises)
.then((arr) => assert.deepEqual(
, ['result a', 'result b', 'result c']
arr; ))
以下示例演示了如果至少有一个输入 Promise 被拒绝会发生什么
const promises = [
Promise.resolve('result a'),
Promise.resolve('result b'),
Promise.reject('ERROR'),
;
]Promise.all(promises)
.catch((err) => assert.equal(
, 'ERROR'
err; ))
图 23 说明了 Promise.all()
的工作原理。
Promise.all()
实现异步 .map()
数组转换方法(如 .map()
、.filter()
等)适用于同步计算。例如
function timesTwoSync(x) {
return 2 * x;
}const arr = [1, 2, 3];
const result = arr.map(timesTwoSync);
.deepEqual(result, [2, 4, 6]); assert
如果 .map()
的回调是一个基于 Promise 的函数(将普通值映射到 Promise 的函数),会发生什么?那么 .map()
的结果是一个 Promise 数组。唉,这不是普通代码可以处理的数据。值得庆幸的是,我们可以通过 Promise.all()
来解决这个问题:它将 Promise 数组转换为一个 Promise,该 Promise 将使用普通值数组完成。
function timesTwoAsync(x) {
return new Promise(resolve => resolve(x * 2));
}const arr = [1, 2, 3];
const promiseArr = arr.map(timesTwoAsync);
Promise.all(promiseArr)
.then(result => {
.deepEqual(result, [2, 4, 6]);
assert; })
.map()
示例接下来,我们将使用 .map()
和 Promise.all()
从 Web 下载文本文件。为此,我们需要以下工具函数
function downloadText(url) {
return fetch(url)
.then((response) => { // (A)
if (!response.ok) { // (B)
throw new Error(response.statusText);
}return response.text(); // (C)
;
}) }
downloadText()
使用基于 Promise 的 fetch API 将文本文件下载为字符串
response
(A 行)。response.ok
(B 行)检查是否有错误,例如“找不到文件”。.text()
(C 行)将文件的内容检索为字符串。在以下示例中,我们下载了两个文本文件
const urls = [
'http://example.com/first.txt',
'http://example.com/second.txt',
;
]
const promises = urls.map(
=> downloadText(url));
url
Promise.all(promises)
.then(
=> assert.deepEqual(
(arr) , ['First!', 'Second!']
arr; ))
Promise.all()
的简单实现这是 Promise.all()
的简化实现(例如,它不执行安全检查)
function all(iterable) {
return new Promise((resolve, reject) => {
let elementCount = 0;
let result;
let index = 0;
for (const promise of iterable) {
// Preserve the current value of `index`
const currentIndex = index;
.then(
promise=> {
(value) = value;
result[currentIndex] ++;
elementCountif (elementCount === result.length) {
resolve(result); // (A)
},
}=> {
(err) reject(err); // (B)
;
})++;
index
}if (index === 0) {
resolve([]);
return;
}// Now we know how many Promises there are in `iterable`.
// We can wait until now with initializing `result` because
// the callbacks of .then() are executed asynchronously.
= new Array(index);
result ;
}) }
结果 Promise 被解决的两个主要位置是 A 行和 B 行。其中一个解决后,另一个就不能再更改解决值了,因为一个 Promise 只能被解决一次。
Promise.race()
这是 Promise.race()
的类型签名
Promise.race<T>(promises: Iterable<Promise<T>>): Promise<T>
Promise.race()
返回一个 Promise q
,一旦 promises
中的第一个 Promise p
被解决,它就会被解决。q
具有与 p
相同的解决值。
在以下演示中,已完成的 Promise 的解决(A 行)发生在被拒绝的 Promise 的解决(B 行)之前。因此,结果也被完成了(C 行)。
const promises = [
new Promise((resolve, reject) =>
setTimeout(() => resolve('result'), 100)), // (A)
new Promise((resolve, reject) =>
setTimeout(() => reject('ERROR'), 200)), // (B)
;
]Promise.race(promises)
.then((result) => assert.equal( // (C)
, 'result')); result
在下一个演示中,拒绝先发生
const promises = [
new Promise((resolve, reject) =>
setTimeout(() => resolve('result'), 200)),
new Promise((resolve, reject) =>
setTimeout(() => reject('ERROR'), 100)),
;
]Promise.race(promises)
.then(
=> assert.fail(),
(result) => assert.equal(
(err) , 'ERROR')); err
请注意,Promise.race()
返回的 Promise 会在其输入 Promise 中的第一个 Promise 被解决后立即解决。这意味着 Promise.race([])
的结果永远不会被解决。
图 24 说明了 Promise.race()
的工作原理。
Promise.race()
使 Promise 超时在本节中,我们将使用 Promise.race()
使 Promise 超时。以下辅助函数将在多次使用时很有用
function resolveAfter(ms, value=undefined) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), ms);
;
}) }
resolveAfter()
返回一个 Promise,该 Promise 在 ms
毫秒后使用 value
解决。
此函数使 Promise 超时
function timeout(timeoutInMs, promise) {
return Promise.race([
,
promiseresolveAfter(timeoutInMs,
Promise.reject(new Error('Operation timed out'))),
;
]) }
timeout()
返回一个 Promise,其解决与以下两个 Promise 中先解决的那个相同
promise
timeoutInMs
毫秒后被拒绝的 Promise为了生成第二个 Promise,timeout()
利用了这样一个事实,即使用被拒绝的 Promise 解决一个待定的 Promise 会导致前者被拒绝。
让我们看看 timeout()
的实际效果。在这里,输入 Promise 在超时之前就已完成。因此,输出 Promise 也已完成。
timeout(200, resolveAfter(100, 'Result!'))
.then(result => assert.equal(result, 'Result!'));
在这里,超时发生在输入 Promise 完成之前。因此,输出 Promise 被拒绝。
timeout(100, resolveAfter(2000, 'Result!'))
.catch(err => assert.deepEqual(err, new Error('Operation timed out')));
重要的是要理解“使 Promise 超时”的真正含义
也就是说,超时只会阻止输入 Promise 影响输出(因为一个 Promise 只能被解决一次)。但它不会停止生成输入 Promise 的异步操作。
Promise.race()
的简单实现这是 Promise.race()
的简化实现(例如,它不执行安全检查)
function race(iterable) {
return new Promise((resolve, reject) => {
for (const promise of iterable) {
.then(
promise=> {
(value) resolve(value); // (A)
,
}=> {
(err) reject(err); // (B)
;
})
};
}) }
结果 Promise 在 A 行或 B 行被解决。一旦被解决,解决值就不能再更改了。
Promise.any()
和 AggregateError
[ES2021]这是 Promise.any()
的类型签名
Promise.any<T>(promises: Iterable<Promise<T>>): Promise<T>
Promise.any()
返回一个 Promise p
。它的解决方式取决于参数 promises
(它指的是一个 Promise 的可迭代对象)
p
将使用该 Promise 解决。p
将使用 AggregateError
的实例拒绝,该实例包含所有拒绝值。这是 AggregateError
(Error
的子类)的类型签名
class AggregateError extends Error {
// Instance properties (complementing the ones of Error)
: Array<any>;
errors
constructor(
: Iterable<any>,
errors: string = '',
message?: ErrorOptions // ES2022
options;
)
}interface ErrorOptions {
?: any; // ES2022
cause }
图 25 展示了 Promise.any()
的工作原理。
如果一个 Promise 成功解决,就会发生这种情况
const promises = [
Promise.reject('ERROR A'),
Promise.reject('ERROR B'),
Promise.resolve('result'),
;
]Promise.any(promises)
.then((result) => assert.equal(
, 'result'
result; ))
如果所有 Promise 都被拒绝,就会发生这种情况
const promises = [
Promise.reject('ERROR A'),
Promise.reject('ERROR B'),
Promise.reject('ERROR C'),
;
]Promise.any(promises)
.catch((aggregateError) => assert.deepEqual(
.errors,
aggregateError'ERROR A', 'ERROR B', 'ERROR C']
[; ))
Promise.any()
与 Promise.all()
的比较Promise.any()
和 Promise.all()
可以通过两种方式进行比较
Promise.all()
:第一个输入拒绝会拒绝结果 Promise,或者其解决值为包含输入解决值的数组。Promise.any()
:第一个输入解决会解决结果 Promise,或者其拒绝值为包含输入拒绝值的数组(在错误对象内部)。Promise.all()
关注的是*所有*解决。相反的情况(至少有一个拒绝)会导致拒绝。Promise.any()
关注的是第一个解决。相反的情况(只有拒绝)会导致拒绝。Promise.any()
与 Promise.race()
的比较Promise.any()
和 Promise.race()
也有关联,但关注的是不同的事情
Promise.race()
关注的是结果。首先得到结果的 Promise “获胜”。换句话说:我们想知道哪个异步计算首先终止。Promise.any()
关注的是解决。首先得到解决的 Promise “获胜”。换句话说:我们想知道哪个异步计算首先成功。.race()
的主要(相对罕见)用例是 Promise 超时。.any()
的用例更广泛。我们接下来会看看。
Promise.any()
的用例如果我们有多个异步计算,并且我们只对第一个成功的计算感兴趣,则使用 Promise.any()
。在某种程度上,我们让计算相互竞争,并使用最快的那个。
以下代码演示了下载资源时的样子
const resource = await Promise.any([
fetch('http://example.com/first.txt')
.then(response => response.text()),
fetch('http://example.com/second.txt')
.then(response => response.text()),
; ])
相同的模式使我们能够使用下载速度更快的任何模块
const lodash = await Promise.any([
import('https://primary.example.com/lodash'),
import('https://secondary.example.com/lodash'),
; ])
为了进行比较,如果辅助服务器只是一个备用服务器(以防主服务器出现故障),我们会使用以下代码
let lodash;
try {
= await import('https://primary.example.com/lodash');
lodash catch {
} = await import('https://secondary.example.com/lodash');
lodash }
Promise.any()
?Promise.any()
的简单实现基本上是 Promise.all()
实现的镜像版本。
Promise.allSettled()
[ES2020]这一次,类型签名稍微复杂一些。您可以随意跳到第一个演示,它应该更容易理解。
这是 Promise.allSettled()
的类型签名
Promise.allSettled<T>(promises: Iterable<Promise<T>>)
: Promise<Array<SettlementObject<T>>>
它返回一个数组的 Promise,该数组的元素具有以下类型签名
type SettlementObject<T> = FulfillmentObject<T> | RejectionObject;
interface FulfillmentObject<T> {
: 'fulfilled';
status: T;
value
}
interface RejectionObject {
: 'rejected';
status: unknown;
reason }
Promise.allSettled()
返回一个 Promise out
。一旦所有 promises
都已得到结果,out
就会使用一个数组得到解决。该数组的每个元素 e
对应于 promises
的一个 Promise p
如果 p
使用解决值 v
得到解决,则 e
为
status: 'fulfilled', value: v } {
如果 p
使用拒绝值 r
被拒绝,则 e
为
status: 'rejected', reason: r } {
除非在迭代 promises
时出错,否则输出 Promise out
永远不会被拒绝。
图 26 展示了 Promise.allSettled()
的工作原理。
Promise.allSettled()
的第一个演示这是 Promise.allSettled()
工作原理的快速入门演示
Promise.allSettled([
Promise.resolve('a'),
Promise.reject('b'),
]).then(arr => assert.deepEqual(arr, [
status: 'fulfilled', value: 'a' },
{ status: 'rejected', reason: 'b' },
{ ; ]))
Promise.allSettled()
的更长示例下一个示例类似于.map()
加 Promise.all()
示例(我们从中借用了函数 downloadText()
):我们正在下载多个文本文件,这些文件的 URL 存储在一个数组中。但是,这一次,我们不想在出现错误时停止,我们想继续下去。Promise.allSettled()
允许我们这样做
const urls = [
'http://example.com/exists.txt',
'http://example.com/missing.txt',
;
]
const result = Promise.allSettled(
.map(u => downloadText(u)));
urls.then(
result=> assert.deepEqual(
arr ,
arr
[
{status: 'fulfilled',
value: 'Hello!',
,
}
{status: 'rejected',
reason: new Error('Not Found'),
,
}
]; ))
Promise.allSettled()
的简单实现这是 Promise.allSettled()
的简化实现(例如,它不执行安全检查)
function allSettled(iterable) {
return new Promise((resolve, reject) => {
let elementCount = 0;
let result;
function addElementToResult(i, elem) {
= elem;
result[i] ++;
elementCountif (elementCount === result.length) {
resolve(result);
}
}
let index = 0;
for (const promise of iterable) {
// Capture the current value of `index`
const currentIndex = index;
.then(
promise=> addElementToResult(
(value) , {
currentIndexstatus: 'fulfilled',
value,
})=> addElementToResult(
(reason) , {
currentIndexstatus: 'rejected',
reason;
}))++;
index
}if (index === 0) {
resolve([]);
return;
}// Now we know how many Promises there are in `iterable`.
// We can wait until now with initializing `result` because
// the callbacks of .then() are executed asynchronously.
= new Array(index);
result ;
}) }
对于 Promise 组合器,*短路*意味着输出 Promise 会提前得到结果 - 在所有输入 Promise 都得到结果之前。以下组合器会短路
Promise.all()
:一旦一个输入 Promise 被拒绝,输出 Promise 就会被拒绝。Promise.race()
:一旦一个输入 Promise 得到结果,输出 Promise 就会得到结果。Promise.any()
:一旦一个输入 Promise 得到解决,输出 Promise 就会得到解决。再次强调,提前得到结果并不意味着被忽略的 Promise 背后的操作会停止。它只是意味着它们的解决被忽略了。
Promise.all()
(高级)考虑以下代码
const asyncFunc1 = () => Promise.resolve('one');
const asyncFunc2 = () => Promise.resolve('two');
asyncFunc1()
.then(result1 => {
.equal(result1, 'one');
assertreturn asyncFunc2();
}).then(result2 => {
.equal(result2, 'two');
assert; })
以这种方式使用 .then()
会*顺序*执行基于 Promise 的函数:只有在 asyncFunc1()
的结果得到结果后,才会执行 asyncFunc2()
。
Promise.all()
帮助更并发地执行基于 Promise 的函数
Promise.all([asyncFunc1(), asyncFunc2()])
.then(arr => {
.deepEqual(arr, ['one', 'two']);
assert; })
确定异步代码“并发”程度的提示:关注异步操作何时开始,而不是它们的 Promise 如何处理。
例如,以下每个函数都并发执行 asyncFunc1()
和 asyncFunc2()
,因为它们几乎同时启动。
function concurrentAll() {
return Promise.all([asyncFunc1(), asyncFunc2()]);
}
function concurrentThen() {
const p1 = asyncFunc1();
const p2 = asyncFunc2();
return p1.then(r1 => p2.then(r2 => [r1, r2]));
}
另一方面,以下两个函数都顺序执行 asyncFunc1()
和 asyncFunc2()
:asyncFunc2()
仅在 asyncFunc1()
的 Promise 得到解决后才被调用。
function sequentialThen() {
return asyncFunc1()
.then(r1 => asyncFunc2()
.then(r2 => [r1, r2]));
}
function sequentialAll() {
const p1 = asyncFunc1();
const p2 = p1.then(() => asyncFunc2());
return Promise.all([p1, p2]);
}
Promise.all()
是 fork-joinPromise.all()
与并发模式“fork join”松散相关。让我们回顾一下我们之前遇到的一个例子
Promise.all([
// (A) fork
downloadText('http://example.com/first.txt'),
downloadText('http://example.com/second.txt'),
])// (B) join
.then(
=> assert.deepEqual(
(arr) , ['First!', 'Second!']
arr; ))
本节提供链接 Promise 的技巧。
问题
// Don’t do this
function foo() {
const promise = asyncFunc();
.then(result => {
promise// ···
;
})
return promise;
}
计算从 asyncFunc()
返回的 Promise 开始。但之后,计算继续,并通过 .then()
创建另一个 Promise。foo()
返回前一个 Promise,但应该返回后一个 Promise。解决方法如下
function foo() {
const promise = asyncFunc();
return promise.then(result => {
// ···
;
}) }
问题
// Don’t do this
asyncFunc1()
.then(result1 => {
return asyncFunc2()
.then(result2 => { // (A)
// ···
;
}); })
A 行中的 .then()
是嵌套的。扁平结构会更好
asyncFunc1()
.then(result1 => {
return asyncFunc2();
}).then(result2 => {
// ···
; })
这是另一个可以避免的嵌套示例
// Don’t do this
asyncFunc1()
.then(result1 => {
if (result1 < 0) {
return asyncFuncA()
.then(resultA => 'Result: ' + resultA);
else {
} return asyncFuncB()
.then(resultB => 'Result: ' + resultB);
}; })
我们可以再次获得扁平结构
asyncFunc1()
.then(result1 => {
return result1 < 0 ? asyncFuncA() : asyncFuncB();
}).then(resultAB => {
return 'Result: ' + resultAB;
; })
在以下代码中,我们实际上受益于嵌套
.open()
db.then(connection => { // (A)
return connection.select({ name: 'Jane' })
.then(result => { // (B)
// Process result
// Use `connection` to make more queries
})// ···
.finally(() => {
.close(); // (C)
connection;
}) })
我们在 A 行中接收异步结果。在 B 行中,我们进行嵌套,以便我们可以在回调内部和 C 行中访问变量 connection
。
问题
// Don’t do this
class Model {
insertInto(db) {
return new Promise((resolve, reject) => { // (A)
.insert(this.fields)
db.then(resultCode => {
this.notifyObservers({event: 'created', model: this});
resolve(resultCode);
.catch(err => {
})reject(err);
});
})
}// ···
}
在 A 行中,我们正在创建一个 Promise 来传递 db.insert()
的结果。这过于冗长,可以简化
class Model {
insertInto(db) {
return db.insert(this.fields)
.then(resultCode => {
this.notifyObservers({event: 'created', model: this});
return resultCode;
;
})
}// ···
}
关键是我们不需要创建一个 Promise;我们可以返回 .then()
调用的结果。另一个好处是我们不需要捕获并重新拒绝 db.insert()
的失败。我们只需将其拒绝传递给 .insertInto()
的调用者。
除非另有说明,否则该功能是在 ECMAScript 6 中引入的(这是 Promise 被添加到该语言中的时间)。
词汇表
Promise.all()
Promise.all<T>(promises: Iterable<Promise<T>>)
: Promise<Array<T>>
P
的**解决**:如果所有输入 Promise 都得到解决。P
的**拒绝**:如果一个输入 Promise 被拒绝。Promise.race()
Promise.race<T>(promises: Iterable<Promise<T>>)
: Promise<T>
P
的**结果**:如果第一个输入 Promise 得到结果。Promise.any()
[ES2021]Promise.any<T>(promises: Iterable<Promise<T>>): Promise<T>
P
的**解决**:如果一个输入 Promise 得到解决。P
的**拒绝**:如果所有输入 Promise 都被拒绝。AggregateError
。这是 AggregateError
的类型签名(省略了一些成员)
class AggregateError {
constructor(errors: Iterable<any>, message: string);
get errors(): Array<any>;
get message(): string;
}
Promise.allSettled()
[ES2020]Promise.allSettled<T>(promises: Iterable<Promise<T>>)
: Promise<Array<SettlementObject<T>>>
P
的**解决**:如果所有输入 Promise 都得到结果。P
的**拒绝**:如果在迭代输入 Promise 时出错。这是 SettlementObject
的类型签名
type SettlementObject<T> = FulfillmentObject<T> | RejectionObject;
interface FulfillmentObject<T> {
: 'fulfilled';
status: T;
value
}
interface RejectionObject {
: 'rejected';
status: unknown;
reason }