做者:Dr. Axel Rauschmayerhtml
译者:前端小智前端
来源:2alitygit
阿里云最近在作活动,低至2折,有兴趣能够看看:promotion.aliyun.com/ntms/yunpar…github
为了保证的可读性,本文采用意译而非直译。编程
从ES6 开始,咱们大都使用的是 Promise.all()
和Promise.race()
,Promise.allSettled()
提案已经到第4阶段,所以将会成为ECMAScript 2020
的一部分。后端
Promise.all(promises: Iterable<Promise>): Promise<Array>数组
Promise.all(iterable)
方法返回一个 Promise
实例,此实例在 iterable
参数内全部的 promise
都“完成(resolved)”或参数中不包含 promise
时回调完成(resolve);若是参数中 promise
有一个失败(rejected),此实例回调失败(reject),失败缘由的是第一个失败 promise
的结果Promise.race(promises: Iterable<Promise>): Promisepromise
promise
,一旦迭代器中的某个promise
解决或拒绝,返回的 promise
就会解决或拒绝。Promise.allSettled(promises: Iterable<Promise>): Promise<Array<SettlementObject>>安全
promise
,该promise
在全部给定的promise
已被解析或被拒绝后解析,而且每一个对象都描述每一个promise
的结果。给定一个返回Promise
的异步操做,如下这些是Promise
的可能状态:并发
Promise
要么被完成,要么被拒绝。Promise
一旦达成,它的状态就再也不改变。又称部分-总体模式,将对象整合成树形结构以表示“部分总体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具备一致性,它基于两种函数:
对于 JS 的 Promises 来讲
基元函数包括:Promise.resolve()
、Promise.reject()
组合函数:Promise.all()
, Promise.race()
, Promise.allSettled()
Promise.all()
的类型签名:
返回状况:
完成(Fulfillment): 若是传入的可迭代对象为空,Promise.all
会同步地返回一个已完成(resolved
)状态的promise
。 若是全部传入的 promise
都变为完成状态,或者传入的可迭代对象内没有 promise
,Promise.all
返回的 promise
异步地变为完成。 在任何状况下,Promise.all
返回的 promise
的完成状态的结果都是一个数组,它包含全部的传入迭代参数对象的值(也包括非 promise 值)。
失败/拒绝(Rejection): 若是传入的 promise
中有一个失败(rejected
),Promise.all
异步地将失败的那个结果给失败状态的回调函数,而无论其它 promise
是否完成。
来个例子:
const promises = [
Promise.resolve('a'),
Promise.resolve('b'),
Promise.resolve('c'),
];
Promise.all(promises)
.then((arr) => assert.deepEqual(
arr, ['a', 'b', 'c']
));
复制代码
若是其中的一个 promise 被拒绝,那么又是什么状况:
const promises = [
Promise.resolve('a'),
Promise.resolve('b'),
Promise.reject('ERROR'),
];
Promise.all(promises)
.catch((err) => assert.equal(
err, 'ERROR'
));
复制代码
下图说明Promise.all()
是如何工做的
数组转换方法,如.map()
、.filter()
等,用于同步计算。例如
function timesTwoSync(x) {
return 2 * x;
}
const arr = [1, 2, 3];
const result = arr.map(timesTwoSync);
assert.deepEqual(result, [2, 4, 6]);
复制代码
若是.map()
的回调是基于Promise
的函数会发生什么? 使用这种方式 .map()
返回的的结果是一个Promises
数组。
Promises
数组不是普通代码可使用的数据,但咱们能够经过Promise.all()
来解决这个问题:它将Promises数组转换为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 => {
assert.deepEqual(result, [2, 4, 6]);
});
复制代码
接下来,我们使用.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 以字符串流的方式下载文件:
首先,它异步检索响应(第A行)。
response.ok(B行)检查是否存在“找不到文件”等错误。
若是没有错误,使用.text()
(第C行)以字符串的形式取回文件的内容。
在下面的示例中,我们 下载了两个文件
const urls = [
'http://example.com/first.txt',
'http://example.com/second.txt',
];
const promises = urls.map(
url => downloadText(url));
Promise.all(promises)
.then(
(arr) => assert.deepEqual(
arr, ['First!', 'Second!']
));
复制代码
function all(iterable) {
return new Promise((resolve, reject) => {
let index = 0;
for (const promise of iterable) {
// Capture the current value of `index`
const currentIndex = index;
promise.then(
(value) => {
if (anErrorOccurred) return;
result[currentIndex] = value;
elementCount++;
if (elementCount === result.length) {
resolve(result);
}
},
(err) => {
if (anErrorOccurred) return;
anErrorOccurred = true;
reject(err);
});
index++;
}
if (index === 0) {
resolve([]);
return;
}
let elementCount = 0;
let anErrorOccurred = false;
const result = new Array(index);
});
}
复制代码
##5. Promise.race()
Promise.race()
方法的定义:
Promise.race(promises: Iterable<Promise>): Promise
Promise.race(iterable) 方法返回一个 promise
,一旦迭代器中的某个promise
解决或拒绝,返回的 promise
就会解决或拒绝。来几个例子,瞧瞧:
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'));
复制代码
在第 A
行,Promise
是完成状态 ,因此 第 C
行会执行(尽管第 B
行被拒绝)。
若是 Promise 被拒绝首先执行,在来看看状况是嘛样的:
const promises = [
new Promise((resolve, reject) =>
setTimeout(() => resolve('result'), 200)),
new Promise((resolve, reject) =>
setTimeout(() => reject('ERROR'), 100)),
];
Promise.race(promises)
.then(
(result) => assert.fail(),
(err) => assert.equal(
err, 'ERROR'));
复制代码
注意,因为 Promse
先被拒绝,因此 Promise.race()
返回的是一个被拒绝的 Promise
这意味着Promise.race([])
的结果永远不会完成。
下图演示了Promise.race()
的工做原理:
在本节中,咱们将使用Promise.race()
来处理超时的 Promise
。 如下辅助函数:
function resolveAfter(ms, value=undefined) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), ms);
});
}
复制代码
resolveAfter()
主要作的是在指定的时间内,返回一个状态为 resolve
的 Promise
,值为为传入的 value
调用上面方法:
function timeout(timeoutInMs, promise) {
return Promise.race([
promise,
resolveAfter(timeoutInMs,
Promise.reject(new Error('Operation timed out'))),
]);
}
复制代码
timeout()
返回一个Promise
,该 Promise
的状态取决于传入 promise
状态 。
其中 timeout
函数中的 resolveAfter(timeoutInMs, Promise.reject(new Error('Operation timed out'))
,经过 resolveAfter
定义可知,该结果返回的是一个被拒绝状态的 Promise
。
再来看看timeout(timeoutInMs, promise)
的运行状况。若是传入promise
在指定的时间以前状态为完成时,timeout
返回结果就是一个完成状态的 Promise
,能够经过.then
的第一个回调参数处理返回的结果。
timeout(200, resolveAfter(100, 'Result!'))
.then(result => assert.equal(result, 'Result!'));
复制代码
相反,若是是在指定的时间以后完成,刚 timeout
返回结果就是一个拒绝状态的 Promise
,从而触发catch
方法指定的回调函数。
timeout(100, resolveAfter(2000, 'Result!'))
.catch(err => assert.deepEqual(err, new Error('Operation timed out')));
复制代码
重要的是要了解“Promise 超时”的真正含义:
Promise
较到的获得解决,其结果就会给返回的 Promise
。Promise
的状态为拒绝。也就是说,超时只会阻止传入的Promise,影响输出 Promise(由于Promise只能解决一次), 但它并无阻止传入Promise
的异步操做。
如下是 Promise.race()
的一个简化实现(它不执行安全检查)
function race(iterable) {
return new Promise((resolve, reject) => {
for (const promise of iterable) {
promise.then(
(value) => {
if (settlementOccurred) return;
settlementOccurred = true;
resolve(value);
},
(err) => {
if (settlementOccurred) return;
settlementOccurred = true;
reject(err);
});
}
let settlementOccurred = false;
});
}
复制代码
“Promise.allSettled”
这一特性是由Jason Williams,Robert Pamely和Mathias Bynens提出。
promise.allsettle()
方法的定义:
它返回一个Array
的Promise
,其元素具备如下类型特征:
type SettlementObject<T> = FulfillmentObject<T> | RejectionObject;
interface FulfillmentObject<T> {
status: 'fulfilled';
value: T;
}
interface RejectionObject {
status: 'rejected';
reason: unknown;
}
复制代码
Promise.allSettled()
方法返回一个promise,该promise在全部给定的promise已被解析或被拒绝后解析,而且每一个对象都描述每一个promise的结果。
举例说明, 好比各位用户在页面上面同时填了3个独立的表单, 这三个表单分三个接口提交到后端, 三个接口独立, 没有顺序依赖, 这个时候咱们须要等到请求所有完成后给与用户提示表单提交的状况
在多个promise
同时进行时我们很快会想到使用Promise.all
来进行包装, 可是因为Promise.all
的短路特性, 三个提交中若前面任意一个提交失败, 则后面的表单也不会进行提交了, 这就与我们需求不符合.
Promise.allSettled
跟Promise.all
相似, 其参数接受一个Promise
的数组, 返回一个新的Promise
, 惟一的不一样在于, 其不会进行短路, 也就是说当Promise
所有处理完成后咱们能够拿到每一个Promise
的状态, 而无论其是否处理成功.
下图说明promise.allsettle()
是如何工做的
这是Promise.allSettled()
使用方式快速演示示例
Promise.allSettled([
Promise.resolve('a'),
Promise.reject('b'),
])
.then(arr => assert.deepEqual(arr, [
{ status: 'fulfilled', value: 'a' },
{ status: 'rejected', reason: 'b' },
]));
复制代码
这个示例相似于.map()
和Promise.all()
示例(咱们从其中借用了downloadText()
函数):咱们下载多个文本文件,这些文件的url
存储在一个数组中。可是,这一次,我们不但愿在出现错误时中止,而是但愿继续执行。Promise.allSettled()
容许我们这样作:
const urls = [
'http://example.com/exists.txt',
'http://example.com/missing.txt',
];
const result = Promise.allSettled(
urls.map(u => downloadText(u)));
result.then(
arr => assert.deepEqual(
arr,
[
{
status: 'fulfilled',
value: 'Hello!',
},
{
status: 'rejected',
reason: new Error('Not Found'),
},
]
));
复制代码
这是promise.allsettle()
的简化实现(不执行安全检查)
function allSettled(iterable) {
return new Promise((resolve, reject) => {
function addElementToResult(i, elem) {
result[i] = elem;
elementCount++;
if (elementCount === result.length) {
resolve(result);
}
}
let index = 0;
for (const promise of iterable) {
// Capture the current value of `index`
const currentIndex = index;
promise.then(
(value) => addElementToResult(
currentIndex, {
status: 'fulfilled',
value
}),
(reason) => addElementToResult(
currentIndex, {
status: 'rejected',
reason
}));
index++;
}
if (index === 0) {
resolve([]);
return;
}
let elementCount = 0;
const result = new Array(index);
});
}
复制代码
Promise.all()
和 romise.race()
都具备 短路特性
promise
有一个失败(rejected),此实例回调失败(reject)Promise.race():若是参数中某个promise
解决或拒绝,返回的 promise就会解决或拒绝。
考虑下面的代码:
asyncFunc1()
.then(result1 => {
assert.equal(result1, 'one');
return asyncFunc2();
})
.then(result2 => {
assert.equal(result2, 'two');
});
复制代码
使用.then()
顺序执行基于Promise
的函数:只有在 asyncFunc1()
的结果被解决后才会执行asyncFunc2()
。
而 Promise.all()
是并发执行的
Promise.all([asyncFunc1(), asyncFunc2()])
.then(arr => {
assert.deepEqual(arr, ['one', 'two']);
});
复制代码
肯定并发异步代码的技巧:关注异步操做什么时候启动,而不是如何处理它们的Promises。
例如,下面的每一个函数都同时执行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()
的解决以后才调用。
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 join”松散相关。重温一下我们前面的一个例子:
Promise.all([
// (A) fork
downloadText('http://example.com/first.txt'),
downloadText('http://example.com/second.txt'),
])
// (B) join
.then(
(arr) => assert.deepEqual(
arr, ['First!', 'Second!']
));
复制代码
A
行中,分割两个异步任务并同时执行它们。B
行中,对每一个小任务获得的结果进行汇总。代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug。
为了回馈读者,《大迁世界》不按期举行(每月一到三次),现金抽奖活动,保底200,外加用户赞扬,但愿你能成为大迁世界的小锦鲤,快来试试吧
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。
每次整理文章,通常都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励