# Promise
# Promise
可以把 Promise 看成一个状态机。初始是 pending
状态,可以通过函数 resolve
和 reject
,将状态转变为fulfilled
或者rejected
状态,状态一旦改变就不能再次变化
三种状态:
- pending: 初始状态
- fulfilled: 成功完成
- rejected: 操作失败
then 函数会返回一个Promise
实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了
# 链式流
- 每次对Promise的调用 then(),他都会创建并返回一个新的Promise,我们可以将其链接起来
- 不管从then()调用完成回调(第一个参数)返回的值是什么,它都会被自动设置为被链接Promise(第一点中的)完成
var p1 = new Promise((resolve, reject)=> {
resolve(66);
})
//和上面的写法等效
var p2 = Promise.resolve(66);
延迟
function deplay(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time)
})
}
# API
# all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值且此时其他的请求被放弃
(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
await Promise().all([p1,p2,p3])
失败的情况
var p1 = Promise.resolve(3);
var p2 = Promise.reject(2);
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // 永远走不到这里
}).catch(function (err) {
console.log(err); // 2
});
# race
Promise.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// expected output: "two"
# catch
catch只接受一个拒绝回调作为参数,并自动替换默认完成回调,所以catch 等同于
then(null)
then()和catch()都会返回一个新的promise
new Promise((resolve, reject) => {
reject('1111');
}).then((x, y) => {
console.log(x);
}, (x) => {
console.log(x); // >>>> 1111 这是then的第二个参数,并不是新的then
});
# any
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value);
// pFast fulfils first
})
// 期望输出: "很快完成"
如果没有 fulfilled (成功的) promise,Promise.any() 返回 AggregateError 错误。
const pErr = new Promise((resolve, reject) => {
reject('总是失败');
});
Promise.any([pErr]).catch((err) => {
console.log(err);
})
// 期望输出: "AggregateError: No Promise in Promise.any was resolved"
# allSettled
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由ES2020
引入
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();
上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。该方法返回的新的 Promise 实例,一旦结束,状态总是
fulfilled
,不会变成rejected
。状态变成fulfilled
后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()
的 Promise 实例
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
上面代码中,Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的监听函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的两个 Promise 实例。每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejected。fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// 过滤出失败的请求,并输出原因
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()
方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。
Promise.all()方法无法做到这一点。
const urls = [ /* ... */ ];
const requests = urls.map(x => fetch(x));
try {
await Promise.all(requests);
console.log('所有请求都成功。');
} catch {
console.log('至少一个请求失败,其他请求可能还没结束。');
}
上面代码中,Promise.all()
无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了Promise.allSettled()
,这就很容易了
# Promise 局限性
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)