ES6 Promise相关用法详解
Promise
是异步编程的一种解决方案,异步编程的历程经历了:回调函数和事件 -> Promise
-> Async/await
。
相较于传统的回调函数和事件,Promise
减少了回调函数可能引发的回调地狱问题,是代码结构有了进一步的提升。
初步认识
Promise
是一个对象,它代表了一个异步操作的最终完成或者失败。
在ES6
中,Promise
对象存在三种状态:
- Pending(进行中): 初始状态,表示 Promise 对象正在进行中,尚未成功(resolve)或失败(reject)。
- Fulfilled(已成功, 又称Resolved): 表示 Promise 对象已成功地完成了异步操作,执行了
resolve
回调函数。 - Rejected(已失败): 表示 Promise 对象已经失败,执行了
reject
回调函数。
除此之外,Promise
对象有以下两个特点:
- 对象的状态不受外界影响: 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变:
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要发生上述情况,Promise的状态就凝固了,任何时候都可以得到这个结果。即使这时候再对Promise
对象添加回调函数,也会立即得到这个结果。
// 创建一个 Promise 对象
const promise = new Promise((resolve, reject) => {
// 模拟异步操作,1秒后将 Promise 对象状态设置为已完成,并传递值为 "成功"
console.log(new Date().getTime())
setTimeout(() => {
resolve('成功');
console.log(new Date().getTime())
}, 1000)
});
// 添加成功态的回调函数
promise.then(
value => {
console.log(new Date().getTime())
console.log('成功态回调函数被执行:', value);
}
);
Promise
的缺点:
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数,Promise内部抛出的错误不会反应到外部。
- 当处于
pending
状态 时,无法得知目前进展到哪一个阶段
语法
Promise.resolve()
将现有对象转为Promise
对象,且该Promise
对象的状态为resolve
,通常用then
处理回调函数。
Promise.resolve
方法的参数:
- 一个Promise实例:
Promise.resolve
将不做任何修改、原封不动地返回这个实例。 - 一个
thenable
对象:thenable
对象指的是具有then
方法的对象。Promise.resolve
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then
方法。 - 原始值:
Promise.resolve
方法返回一个新的 Promise 对象,状态为resolved
,且这个原始值,作为成功异步的返回值。 - 不带有任何参数:
Promise.resolve
直接返回一个resolved
状态的 Promise 对象
let promise = Promise.resolve($.ajax('/whatever.json'))
// 等价于
let promise = new Promise(resolve => resolve('foo'))
// Promise.resolve方法的参数
// thenable对象参数
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
// 原始值
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s) // Hello
});
// 不带参数
const p = Promise.resolve(); // p就是一个 Promise 对象。
Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
Promise.reject()
方法的参数与Promise.resolve()大体
一致,区别在于 thenable
对象 会原封不动地作为reject
的理由,变成后续的参数。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable) // true
})
处理Promise抛出内部问题的两种方法:
// 1、使用then(successFn, errorFn)
// successFn: 成功回调函数,errorFn: 拒绝的回调函数
p.then(null, function (s) {
console.log(s)
});
// 2、使用catch(err)
p.catch(function (s) {
console.log(s)
});
Promise.try()
实际开发中,经常遇到一种情况:不知道或者不想区分,函数fn
是同步函数还是异步操作,但是想用 Promise 来处理它。
这样的话都可以用 then
方法指定下一个流程,且不管fn
是不是包含异步函数,fn
抛出的错误还可以在catch
中进行处理。
但如果fn
是同步函数,那么它会在本轮事件循环的末尾执行。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
Promise.try()
就是解决了上述问题,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API
,除此之外,其还能用catch
接收同步函数的抛出的错误,这点区别于普通的Promise对象
const f = () => console.log('now');
Promise.try(f).then(...).catch(...);
console.log('next');
// now
// next
Promise.try
就是模拟try
代码块,就像promise.catch
模拟的是catch
代码块。
其它语法
-
Promise.then():
then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。 -
Promise.catch():
用于指定发生错误时的回调函数,是.then(null, rejection)
或.then(undefined, rejection)
的别名 -
Promise.finally():
用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是ES2018
引入标准的。
注意:
- 嵌套的
catch
只会捕获其作用域及以下的错误,而不会捕获链中更高层的错误。
doSomethingCritical()
.then((result) =>
doSomethingOptional() // 可选操作
.then((optionalResult) => doSomethingExtraNice(optionalResult))
.catch((e) => {
console.log(e.message);
}),
) // 即便可选操作失败了,也会继续执行
.then(() => moreCriticalStuff())
.catch((e) => console.log(`严重失败:${e.message}`));
// 内部的 catch 语句仅能捕获到 doSomethingOptional() 和 doSomethingExtraNice() 的失败
// 并将该错误与外界屏蔽,之后就恢复到 moreCriticalStuff() 继续执行。
Catch
的后续链式操作有可能会在一个回调失败之后继续使用链式操作
new Promise((resolve, reject) => {
console.log("初始化");
resolve();
}).then(() => {
throw new Error("有哪里不对了");
}).catch(() => {
console.log("执行「那个」");
}).then(() => {
console.log("执行「这个」,无论前面发生了什么");
});
// 初始化
// 执行「那个」
// 执行「这个」,无论前面发生了什么
Promise的组合工具
Promise有四个组合工具可用来并发异步操作:Promise.all()
、Promise.allSettled()
、Promise.any()
和 Promise.race()
。
Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
语法: const p = Promise.all([p1, p2, p3]);
参数说明:
p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
Promise.all()
返回实例的状态情况:
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数,其元素顺序与传入的 promise 一致。 - 只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 42, "foo"]
});
注意:如果作为参数的 Promise 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法,实例请看阮一峰Promise.all()。
Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。Promise.race()
方法的参数与Promise.all()
方法一样。
但与**Promise.all()
的区别**在于:
这个返回的 promise 会随着第一个 promise 的敲定而敲定(如果第一个敲定的 promise 被兑现,那么返回的 promise 也会被兑现;如果第一个敲定的 promise 被拒绝,那么返回的 promise 也会被拒绝),返回值也是第一个被敲定对象的返回值。
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); // two
});
Promise.allSettled()
Promise.allSettled()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。参数传递与上述两个工具相同。
区别在于,Promise.allSettled()
只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。
返回的结果是带有描述每个 Promise 结果的对象数组,下面是返回结果对象属性说明:
status:
表示 promise 的最终状态,只有"fulfilled"
和"rejected"
两种值value:
仅当status
为"fulfilled"
,才存在。promise
兑现的值。reason:
仅当status
为"rejected"
,才存在,promsie
拒绝的原因。
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.any()
Promise.any()
方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。参数传递与上述三个工具相同。
区别在于,Promise.any()
只要参数实例有一个变成 fulfilled
状态,包装实例就会变成fulfilled
状态,并返回第一个兑现的值。;如果所有参数实例都变成 rejected
状态,包装实例就会变成rejected
状态,返回一个包含拒绝原因数组的 AggregateError
拒绝。
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected
状态而结束
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
参考资料:
阮一峰-Promise 对象:https://www.bookstack.cn/read/es6-3rd/docs-promise.md
使用 Promise:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises