1. 什么是Promise
Promise是ES6(2015年)推出的新语法,所有现代浏览器都支持Promise,可以通过 new 操作符来实例化,是主导性的异步编程机制。
以下是自己对Promise的基本概念理解与总结,不喜勿喷。
- Promise本身不是异步,它只是连接同步和异步的一个桥梁,它只是处理异步计算的编程模式,它只是一个ES6的一个API
- Promise用于异步计算,将异步操作队列话,按照期望的顺序执行,返回符合预期的结果
- Promise有三种状态,只能从 (pedding到resolve) 或者从 (pedding到reject),状态不可逆
- resolve触发then方法的回调,reject触发catch方法的回调
- try/catch的catch无法直接捕获reject抛出的错误,捕获错误需要配合async/await
- Promise中抛出的reject错误会阻止当前reject方法体后面的程序执行,但是不会阻止catch的执行
- new Promise(() => {}) 永远是pending状态
2. 一个简单的Promise示例执行过程
输出顺序:12635
new Promise((resolve, reject) => {
console.log(1)
resolve('成功')
console.log(2)
}).then(res => {
console.log(3)
reject('失败')
console.log(4) // 不会执行
}).catch(err => {
console.log(5)
})
console.log(6)
简单解释一下12635输出顺序
- 126属于同步代码,所以会先依次输出126
- 同步代码执行到resolve('成功')时,此时会把then方法推送到异步调用栈中,等到同步代码执行完成在执行异步调用栈(也就是then方法)
- 执行then方法,输出3,在执行到reject('失败')报失败,reject方法体后面的程序不在执行(所以4不会输出),直接触发catch方法,最后输出5
3. Promise.resolve()、Promise.reject()
Promise.resolve()、Promise.reject()是Promise提供的两个静态方法
- 作用是实例化一个期约,也可以理解为实例化一个成功/失败的Promise对象
- 以下p1和p2两个Promise实例实际上是一样的,只有在需要处理复杂逻辑的时候才需要使用new Promise()对象,一般推荐使用Promise.resolve()
- 如果Promise.resolve()本身传入的参数也是一个期约,那这个行为就类似于空包装,所以说Promise.resolve()是一个幂等方法
- Promise.resolve()可以包装任何值,包括Promise.resolve(null), 返回null的Promise对象
- 如果没有提供这个处理程序,则Promise.resolve()就会包装上一个期约解决之后的值。如果没有显式的返回语句,则Promise.resolve()会包装默认的返回值undefined的Promise对象。
p1和p2相等
// p1和p2都返回undefined的Promise对象
const p1 = new Promise((resolve) => resolve()) // Promise <resolved>: undefined
const p2 = Promise.resolve() // Promise <resolved>: undefined
幂等
// 如下三等式都是返回一个Promise对象,成功/失败的结果都是7,这个可以叫做幂等方法
Promise.resolve(7) == Promise.resolve(Promise.resolve(7)) == Promise.resolve(Promise.resolve(Promise.resolve(7))) // Promise {<fulfilled>: 7}
Promise.reject(7) == Promise.reject(Promise.reject(7)) == Promise.reject(Promise.reject(Promise.reject(7))) // Promise {<rejected>: 7}
返回Promise对象
let p1 = Promise.resolve('foo'); // Promise <resolved>: foo
let p2 = p1.then(); // Promise <resolved>: foo
let p3 = p1.then(() => undefined); // Promise <resolved>: undefined
let p4 = p1.then(() => {}); // Promise <resolved>: undefined
let p5 = p1.then(() => Promise.resolve()); // Promise <resolved>: undefined
let p6 = p1.then(() => 'bar'); // Promise <resolved>: bar
let p7 = p1.then(() => Promise.resolve('bar')); // Promise <resolved>: bar
4. Promise.prototype.then()、Promise.prototype.catch()
- then方法可以接受两个参数,也可以接受一个参数,如果是两个参数:第一个参数是成功时的回调,第二个参数是失败时的回调; 如果是一个参数:只是成功时的回调
Promise.prototype.then(null, onRejected)等价于Promise.prototype.catch()p1.then(() => onResolved('p1'), () => onRejected('p1'))等价于p1.then(() => onResolved('p1')).catch(() => onRejected('p1'))
function onResolved(id) {
setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
setTimeout(console.log, 0, id, 'rejected');
}
const p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));
const p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));
// 如下两种写法实现的功能是一样的
p1.then(() => onResolved('p1'), () => onRejected('p1')); // p1 'resolved'
p2.then(() => onResolved('p2')).catch(() => onRejected('p2')); // p2 'rejected'
5. Promise.prototype.finally()
从pedding到resolved或者rejected任何状态结束时,finally方法都会执行,但是finally没办法知道当前是resolved还是rejected
const p1 = Promise.resolve();
const p2 = Promise.reject();
let onFinally = function() {
setTimeout(console.log, 0, 'Finally!')
}
p1.finally(onFinally)
p2.finally(onFinally)
6. 非重入期约方法
红宝书上名词“非重入期约方法”
- 当期约进入落定状态(.then或.catch),该状态相关的处理程序(回调)会推进调用栈(eventloop),而非立即执行
- 非重入适用于.then、.catch、.finally的回调
let synchronousResolve;
let p = new Promise((resolve) => {
synchronousResolve = function() {
console.log('1: invoking resolve()');
resolve();
console.log('2: resolve() returns');
};
});
p.then(() => console.log('3: then() handler executes'));
synchronousResolve();
console.log('4: synchronousResolve() returns');
// 实际的输出顺序:
// 1: invoking resolve()
// 2: resolve() returns
// 4: synchronousResolve() returns
// 3: then() handler executes
7. 拒绝期约与拒绝错误处理
- Promise的回调里面抛出Error的错误就会使状态pedding到rejected, 即使不主动触发reject()方法
- 正常情况下抛出error错误会阻塞程序的运行,但是在Promise里面抛出错误,实际是从消息队队列里面抛出的错误,并不会阻止同步的程序的运行
const p1 = new Promise((resolve, reject) => {throw new Error('peddiing->rejected')}) // Promise rejected
console.log(111) // 仍会执行
8. 期约连锁与期约合成
每个期约实例的方法then()、catch()、finally()都会返回一个新的期约对象(默认是resolved的promise对象),而这个新的期约实例又有自己的实例方法,就会链式调用
const p = new Promise((resolve) => resolve())
// 111、222、333都会输出
p.then(() => console.log(111))
.then(() => console.log(222))
.then(() => console.log(333))
.catch((err) => console.log(err))
9. Promise.all([])和Promise.race()
- 将多个期约实例组合成一个期约
- Promise.all([])数组至少要包含一个Promise,
- 2.1 数组输出
- 2.2 一次reject会导致整个Promise.all([])程序到达rejected状态, 且只会输出reject的值,resolve的值将忽略
- Promise.race([])是一组期约集合最先resolve或reject的期约镜像,不常用不做过多解释 Promise.all
const p1 = Promise.all([
Promise.resolve(3),
Promise.resolve(),
Promise.resolve(4)
])
p1.then(res => console.log(res)) // [3, undefined, 4]
Promise.race
const p2 = Promise.all([
Promise.resolve(3),
Promise.reject('err'),
Promise.resolve(4)
])
p2.then(res => console.log('res', res)) // 不执行
.catch(err => console.log('err', err)) // 执行输出err