Promise是一个代表异步操作最终完成(包括成功和失败)及结果值的对象。它是一个容器,保存着某个未来才会结束的事件(通常是一个异步操作)的结果。 它实质上是一个构造函数,自己身上有all、reject、resolve这几个的方法,原型上有then、catch等同样很眼熟的方法。 其中,then()方法可以接收两个回调函数作为参数,一个用于处理已解决的promise的值,另一个用于处理被拒绝的promise的原因,其中,第二个函数是可选的,不一定要提供。 ①第一个回调函数是Promise对象的状态改变为resoved时调用(操作成功完成时)。 ②第二个回调函数是Promise对象的状态变为rejected时调用(出现错误时)。 通过使用then()方法,可以将异步操作的处理逻辑与同步操作流程一致地表达出来,避免了传统回调函数嵌套使用带来的问题。
# Promise 的含义
Promise是JS中用于处理可能不立即完成的操作的对象。Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。简单地说,Promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点:
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:①从pending变为fulfilled和②从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点:
- 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 其次,如果不设置回调函数,Promise内部抛出的错误不会反应到外部。
- 第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择。
当创建一个新的Promise实例时,通常会传递一个执行器函数作为参数,该函数接受两个参数:resolve和reject。这两个参数都是函数,分别用于在异步操作成功完成时和失败时调用。 Promise对象有几个重要的方法: ① all(): 接受一个promise对象的数组作为参数,只有当所有promise对象都成功完成时才会解析。 ② reject(): 返回一个新的promise对象,该对象在初始化时就处于拒绝状态。 ③ resolve(): 返回一个新的promise对象,该对象在初始化时就处于已解决状态。 Promise 的原型(Promise.prototype)上也有一些方法,这些方法可以在创建的Promise实例上使用: ① then(): 接受两个参数:一个用于处理已解决的promise的值,另一个用于处理被拒绝的promise的原因,其中,第二个函数是可选的,不一定要提供。。 ② catch(): 用于处理被拒绝的promise的原因。它是then(undefined, onRejected)的简写形式。 这些方法允许我们链式地处理异步操作的结果,并确保即使在异步操作失败时也能执行适当的清理或错误处理代码。
# Promise.resolve()
有时需要将现有对象转为Promise对象,Promise.resolve()方法就起到这个作用。这个方法会返回一个新的Promise实例,该实例的状态为resolved(解决),该方法接受一个参数,该参数会成为后续方法的参数,但这个参数不会改变Promise的状态,除非在链中(.then)提供一个处理函数来接收这个值,并根据需要改变状态。
const jsPromise = Promise.resolve($.ajax('/whatever.json')); //将jQuery生成的deferred对象转为一个新的Promise对象
//Promise.resolve()等价于下面的写法
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
2
3
4
5
Promise.resolve方法的参数分成四种情况:
- 参数是一个Promise实例
如果参数是Promise实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
- 参数是一个thenable对象
thenable对象指的是具有then方法的对象。Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42); //使用resolve回调函数,传递值42
}
};
let p1 = Promise.resolve(thenable); //将thenable对象转换为一个Promise对象
p1.then(function(value) {
// thenable对象的then方法执行后,对象p1的状态就变为resolved,这时这里的这个回调函数会被执行
// 在这里,回调函数会接收到一个参数value,该参数的值为42。
console.log(value); // 42
});
2
3
4
5
6
7
8
9
10
11
- 参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为resolved。回调函数会立即执行,Promise.resolve方法的参数也会同时传给回调函数。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
}); // Hello
//由于字符串Hello不属于异步操作(判断方法是字符串对象不具有then方法),返回Promise实例的状态从一生成就是resolved,
//所以回调函数会立即执行。Promise.resolve方法的参数会同时传给回调函数
2
3
4
5
6
- 不带有任何参数
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的Promise对象。所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve()方法。
const p = Promise.resolve(); //变量p就是一个Promise对象
p.then(function () {
// ...
});
2
3
4
需要注意的是,立即resolve()的Promise对象,是在本轮事件循环(event loop)的结束时执行,而不是在下一轮事件循环的开始时。
setTimeout(function () {
console.log('three');
}, 0); //setTimeout(fn, 0)在下一轮事件循环开始时执行
Promise.resolve().then(function () {
console.log('two');
}); //Promise.resolve()在本轮事件循环结束时执行
console.log('one'); //console.log('one')则是立即执行,因此最先输出
// one
// two
// three
2
3
4
5
6
7
8
9
10
# Promise.reject()
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected(拒绝)。这个方法接受一个参数,即Promise被拒绝的原因,该参数会原封不动地作为reject的理由,变成后续方法的参数。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
}); // 出错了
//上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行。
2
3
4
5
6
7
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
①在Promise.reject()中,你传递的参数将成为拒绝的理由,会影响Promise的状态,具体来说,会将Promise的状态从pending变为rejected。因为reject()方法将Promise对象的状态从未完成变为失败,并在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。 ②在Promise.resolve()中,你传递的参数不会影响Promise的状态,因为Promise.resolve()总是返回一个已解决的Promise,它的状态由你传递的参数决定,但是这个参数不会改变Promise的状态,除非你在链中提供了一个处理函数来接收这个值。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable) //传递给.catch的原因参数确实是原始的thenable对象
}) // true
//上面代码中,Promise.reject方法的参数是一个thenable对象,
//执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象
2
3
4
5
6
7
8
9
10
11
# 应用
# 加载图片
我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve; //将onload事件处理器设置为resolve回调函数,当图像成功加载时,这个函数会被调用,并将Promise的状态更改为已解决(fulfilled)
image.onerror = reject; //将onerror事件处理器设置为reject回调函数。当图像加载失败时(例如,由于路径错误或网络问题),这个函数会被调用,并将Promise的状态更改为已拒绝(rejected)
image.src = path; //设置图像的源路径,即从哪个URL加载图像
});
}; //最后,整个函数返回这个Promise对象,允许调用者使用.then()或.catch()方法处理成功或失败的情况
//一个使用示例可能如下:
preloadImage('/path/to/image.jpg')
.then(function() {
console.log('Image loaded successfully!');
})
.catch(function() {
console.log('Failed to load image.');
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Generator 函数与 Promise 的结合
假设你有一个异步任务列表,并且你需要按照特定的顺序执行这些任务。你可以使用Generator函数和Promise来创建一个可以按照特定顺序执行任务的函数。
使用Generator函数管理流程,遇到异步操作的时候,通常返回一个Promise对象。
function getFoo () { //返回一个Promise,这个Promise在创建时立即被解决,并返回值'foo'
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo(); //使用yield关键字来等待Promise的结果
console.log(foo); //如果Promise成功解决,它将输出解决的值
} catch (e) {
console.log(e); //如果Promise被拒绝,它将捕获错误并输出
}
};
function run (generator) { //运行生成器函数
const it = generator(); //创建生成器函数的迭代器it
function go(result) { //定义一个递归函数go来处理生成器函数的下一步
if (result.done) return result.value; //如果生成器的迭代已经完成,则返回结果的值
return result.value.then(function (value) { //否则,它将等待Promise的结果
return go(it.next(value)); //然后递归地调用生成器的下一个迭代
}, function (error) {
return go(it.throw(error)); //如果Promise被拒绝,它将使用throw方法在生成器中抛出错误
});
}
go(it.next());
}
run(g); //调用run函数并将之前定义的生成器函数g作为参数传递给它。因为getFoo函数立即解决其Promise,所以生成器函数将输出 'foo'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
上面代码的Generator函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。
# Promise.try()
Promise.try是JavaScript中Promise对象的一个静态方法。它用于在Promise链中包装异步操作,确保这些操作是Promise返回的。由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。(让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API )
Promise.try(function() {
// 异步操作
}).then(function(result) {
// 处理结果
}).catch(function(error) { //捕获所有同步和异步的错误
// 处理错误
}); //事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。
2
3
4
5
6
7
# 基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例,生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
2
3
4
5
6
7
8
9
10
11
12
13
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。
- resolve函数的作用是,将Promise对象的状态从未完成变为成功(即从pending变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject函数的作用是,将Promise对象的状态从未完成变为失败(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
- 如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例,即一个异步操作的结果是返回另一个异步操作。
举个例子:
function timeout(ms) { //timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done'); //过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,传递'done'给resolve,这个参数会成为Promise的解析值
});
}
timeout(100).then((value) => { //Promise实例的状态变为resolved,就会触发then方法绑定的回调函数
console.log(value);
}); //done
2
3
4
5
6
7
8
Promise新建后就会立即执行,指的是当Promise对象被创建时,它的executor函数被执行。这里的例子是立即执行resolve。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise 当Promise对象被创建时,它的executor函数被执行,所以首先打印了Promise
// Hi! 它是直接在全局作用域中执行的,同步的,所以它会立即打印,不管前面是否有其他异步操作
// resolved 这是.then回调的输出,它在Promise状态变为resolved后执行
2
3
4
5
6
7
8
9
10
11
如果创建了一个新的Promise对象并设置了resolve或reject的状态,但没有添加.then()或.catch()回调,那么这个Promise的状态改变(无论是变为已解决还是已拒绝)不会引发任何操作。因为Promise的.then()或.catch()方法用于定义当Promise状态变为已解决或已拒绝时应该执行的回调函数。如果没有添加这些回调,那么Promise的状态改变不会产生任何效果。举个例子:
let promise = new Promise((resolve, reject) => {
resolve();
}); //这段代码创建了一个新的Promise,并且立即将其状态设为已解决。但由于没有.then()或.catch()回调函数,这个Promise的状态改变并不会引发任何操作。
//如果你想在Promise状态改变时执行某些操作,你需要添加相应的.then()或.catch()回调函数,像这样:
promise.then(() => {
console.log('Promise is resolved!');
}); //这段代码中,当Promise的状态变为已解决时,会打印出"Promise is resolved!"
2
3
4
5
6
7
举个异步加载图片的例子:
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() { //如果图片加载成功,调用resolve函数并将图片对象作为参数传递,这样外部调用者可以通过.then()方法处理加载完成后的图片
resolve(image);
};
image.onerror = function() { //如果图片加载失败,调用reject函数并传递一个错误对象,这样外部调用者可以通过.catch()方法处理加载失败的情况
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
2
3
4
5
6
7
8
9
10
11
12
举个复杂点的例子:
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000) //p1的状态传递给p2,也就是说,p1的状态决定了p2的状态
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
//上面代码中,p1是一个Promise,3秒后变为rejected。p2的状态在1秒之后改变,resolve方法返回的是p1。
//由于p2返回的是另一个Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。
//所以,后面的then语句都变成针对后者(p1)。又过了2秒,p1变为rejected,导致触发catch方法指定的回调函数。
2
3
4
5
6
7
8
9
10
11
12
13
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
//上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来
//这是因为立即resolved的Promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务
2
3
4
5
6
7
8
9
10
一般来说,调用resolve或reject后,Promise的使命就完成了,后继操作应放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
2
3
4
5
# Promise.prototype.then()
它的作用是为Promise实例添加状态改变时的回调函数。第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
}); //使用then方法依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
2
3
4
5
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数就会等待该Promise对象的状态发生变化,才会被调用。
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
}); //第一个then方法指定的回调函数,返回的是另一个Promise对象。
//这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。
//如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。
2
3
4
5
6
7
8
9
如果采用箭头函数,上面的代码可以写得更简洁。
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
2
3
4
5
6
# Promise.prototype.catch()
用于指定发生错误时的回调函数。如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
2
3
4
5
promise抛出一个错误,就被catch()方法指定的回调函数捕获。举个例子:
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
//比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果Promise状态已经变成resolved,再抛出错误是无效的。Promise在resolve语句后面再抛出错误,不会被捕获,等于没有抛出。因为Promise的状态一旦改变,就永久保持该状态,不会再变了。
Promise 对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
//一共有三个Promise对象:一个由getJSON()产生,两个由then()产生。它们之中任何一个抛出的错误,都会被最后一个catch()捕获。
2
3
4
5
6
7
8
一般来说,不要在then()方法里面定义Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
//bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
//good 这种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。Promise内部的错误不会影响到Promise外部的代码,通俗的说法就是“Promise 会吃掉错误”。举个例子:
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
//someAsyncThing()函数产生的Promise对象内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但不会退出进程、终止脚本执行,2秒后还是会输出123
2
3
4
5
6
7
8
9
10
11
12
13
一般总是建议,Promise对象后面要跟catch()方法,这样可以处理Promise内部发生的错误。catch()方法返回的还是一个Promise对象,因此后面还可以接着调用then()方法。catch()方法之中还能再抛出错误。
# Promise.prototype.finally()
用于指定不管Promise对象最后状态如何,都会执行的操作。finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的Promise状态到底是fulfilled还是rejected。这表明,finally方法里面的操作应该是与状态无关的,不依赖于Promise的执行结果。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
//不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数
2
3
4
5
finally本质上是then方法的特例。
promise
.finally(() => {
// 语句
}); //有了finally方法,则只需要写一次
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
); //如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
finally方法总是会返回原来的值。
Promise.resolve(2).then(() => {}, () => {}) // resolve 的值是 undefined
Promise.resolve(2).finally(() => {}) // resolve 的值是 2
Promise.reject(3).then(() => {}, () => {}) // reject 的值是 undefined
Promise.reject(3).finally(() => {}) // reject 的值是 3
2
3
4
# Promise.all()
用于将多个Promise实例包装成一个新的Promise实例。Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。
在所有promise完成以后再返回所有promise的结果,当所有的Promise都成功,该Promise为完成,返回值是全部Promise返回值的结果数组;如果有一个失败,则该Promise失败,返回最先失败状态的值。
const p = Promise.all([p1, p2, p3]);
//接受一个数组作为参数,p1、p2、p3都是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。
//另外,Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。
2
3
上面代码中,p的状态由p1、p2、p3决定,分成两种情况:
- (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
举个例子:
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));
//booksPromise和userPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommendations这个回调函数
2
3
4
5
6
7
8
9
10
11
注意,如果作为参数的Promise实例自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。如果没有自己的catch方法,就会调用Promise.all()的catch方法。
# Promise.race()
用于将多个Promise实例包装成一个新的Promise实例。这个新的Promise实例会在输入的Promise实例中的**任何一个******率先改变状态(无论是fulfilled还是rejected)时,立即以同样的结果改变状态。如果参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果其中任何一个Promise对象变为rejected状态,则返回的Promise对象也会立即变为rejected状态,并且该Promise对象的结果是第一个变为rejected状态的Promise对象的错误信息。
const p = Promise.race([p1, p2, p3]);
//只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
//p1、p2、p3都是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。
2
3
通常用于设置一个超时机制,为每个Promise设置一个合理的超时时间,如果某个异步操作在指定的时间内未能得到响应,则自动放弃该操作;如果有一个结果获得快,就返回那个结果,不管结果本身是成功状态还是失败状态。下面是一个例子,如果指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
//如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数
2
3
4
5
6
7
8
9
10
# Promise.allSettled()
接受一组Promise实例作为参数,包装成一个新的Promise实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。fulfilled时对象有value属性,rejected时有reason属性,对应两种状态的返回值。
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();//对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失
//该方法返回的新的Promise实例,一旦结束,状态总是fulfilled,不会变成rejected。
//状态变成fulfilled后,Promise的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的Promise实例。
2
3
4
5
6
7
8
9
有时,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。如果没有这个方法,想要确保所有操作都结束就很麻烦。Promise.all()方法无法做到这一点,它无法确定所有请求都结束,想要达到这个目的,写起来很麻烦,有了Promise.allSettled()就很容易了。
# Promise.any()
接受一组Promise实例作为参数,包装成一个新的Promise实例。只要参数实例**有任何一个****变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例*都*****变成rejected状态,包装实例就会变成rejected状态。Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}
//参数数组包含三个Promise操作,其中只要有一个变成fulfilled,Promise.any()返回的Promise对象就变成fulfilled。
//如果所有三个操作都变成rejected,那么await命令就会抛出错误。
2
3
4
5
6
7
8
9
10
11
12
13
Promise.any()抛出的错误不是一个一般的错误,而是一个AggregateError实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。
//下面是 AggregateError 的实现示例------------------------------------------------
new AggregateError() extends Array -> AggregateError
const err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
throw err;
//捕捉错误时,如果不用try...catch结构和await命令,可以像下面这样写---------------------
Promise.any(promises).then(
(first) => {
// Any of the promises was fulfilled.
},
(error) => {
// All of the promises were rejected.
}
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15