promise 基础
在开始正式的之前,我们先思考两个问题:
- promise 的出现主要是为了解决什么问题?
- 让一个异步操作以外部可判断、可控制的的形式返回执行结果,队列形式解决多重回调
- promise 在什么场合被运用了?被广泛运用的原因?
Promise 的状态一经改变就不能再改变。(见 3.1) .then 和.catch 都会返回一个新的 Promise。(上面的 👆1.4 证明了) catch 不管被连接到哪里,都能捕获上层未捕捉过的错误。(见 3.2) 在 Promise 中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如 return 2 会被包装为 return Promise.resolve(2)。
// 除了下面两种
return Promise.reject(new Error('error!!!'))
// or
throw new Error('error!!!')
Promise 的 .then 或者 .catch 可以被调用多次, 但如果 Promise 内部的状态一经改变,并且有了一个值,那么后续每次调用.then 或者.catch 的时候都会直接拿到该值。(见 3.5) .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。 .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环
const promise = Promise.resolve().then(() => {
return promise
})
promise.catch(console.err)
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)
.then 方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为 catch 是.then 第二个参数的简便写法。(见 3.9) .finally 方法也是返回一个 Promise,他在 Promise 结束的时候,无论结果为 resolved 还是 rejected,都会执行里面的回调函数。
先来小题几道
// 构造函数是同步执行,then方法是异步执行,只有等到resolve函数执行时才得到执行
const promise = new Promise((resolve, reject) => {
// 执行同步代码
console.log('1')
// 将promise的状态改为了resolved并且将值保存下来
resolve()
console.log('2')
// 跳出 promise
})
// 遇到promise.then 微任务,加入微任务队列
promise.then(() => {
console.log('3')
})
// 执行同步代码
console.log('4')
- Promise 在生命周期内有三种状态,分别是 pending、fulfilled 或 rejected,状态改变只能是 pending->fulfilled(成功),或者 pending->rejected(失败)。而且状态一旦改变,就不能再次改变。
- Promise 的构造函数中代码是同步执行的,但是 then 方法是异步执行的,then 方法需要等到等到 resolve 函数执行时才得到执行
const promise = new Promise((resolve, reject) => {
resolve('1')
reject('error')
resolve('2')
})
promise
.then((res) => {
console.log('then:', res)
})
.catch((err) => {
console.log('catch:', err)
})
- Promise.resolve() 返回一个 resolved 状态的 promise 实例,所以后面的 then 方法可以执行,then 方法的返回值,作为下一个 then 方法的参数,如果后面跟着的是 catch,如果 catch 后是否还有 then 方法,则跳过 catch 方法向下执行 then 方法
Promise.resolve(1)
.then((res) => {
console.log('then1:', res)
return 2
})
.then((res) => {
console.log('then2:', res)
return 3
})
.catch((err) => {
console.log('catch:', err)
return 4
})
.then((res) => {
console.log('then3:', res)
return 5
})
- 前一个 then 方法的返回值,会作为后一个的参数
Promise.resolve()
.then(() => {
console.log(0)
return Promise.resolve(4) // 返回对象是 thenable,所以又有异常微任务
})
.then((res) => {
console.log(res)
})
- Promise 中包含计时器,Promise 构造函数,先创建了一个宏任务,宏任务执行时,改变 promise 的状态
- 后面遇到已经改变状态的 promise 实例的
.then 方法
则将其添加到本轮中的微任务列表, promise 微任务
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('1')
// 保存结果并将之前的promise.then推入微任务队列
resolve('success')
}, 1000)
})
const start = Date.now()
promise
.then((res) => {
console.log(res, Date.now() - start)
})
.then((res) => {
console.log(res, Date.now() - start)
})
- 异步方法 then 报错,不影响前面 then 的状态,每一个 then 方法都返回一个新的 promise 实例,then 方法报错,可以被后续的 catch 方法捕获,但不能被前面的 catch 方法捕获,所有可以使用 catch 静态方法进行捕获
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('1')
}, 1000)
})
const promise2 = promise.then((res) => {
throw new Error('2')
})
// pending状态
console.log('promise:', promise)
console.log('promise2', promise2)
setTimeout(() => {
// fulfilled状态
console.log('promise:', promise)
// rejected状态
console.log('promise2', promise2)
}, 2000)
- 两个 Promise 实例的 then 链微任务交替执行是按就近原则
new Promise((resolve, reject) => {
console.log('1') // 1. Promise构造函数接受的参数是一个需要立即执行的函数, 是一个同步任务
resolve()
})
.then(() => {
// 2. 注册then方法,把它加到微任务队列
// 3. 没有同步代码,开始执行该微任务
console.log('2')
new Promise((resolve, reject) => {
// 4. 继续执行Promise构造函数
console.log('3')
resolve()
})
.then(() => {
// 5. 注册其then方法,将其加到微任务队列
console.log('4') // 7. 执行
})
.then(() => {
// 8. 注册
console.log('5') // 10. 执行
})
})
.then(() => {
// 6. 没有同步代码,第一个then执行完毕,继续注册外层 Promise 的第二个 then
console.log('6') // 9. 执行
})
- 有 return 语句时的就近原则,需要将 return 表达式执行完毕,才能执行外层第二个 then
new Promise((resolve, reject) => {
console.log('1') // 1. 构造函数的参数,先执行
resolve()
})
// 2. 注册then,添加微任务,没有同步任务,执行其微任务
.then(() => {
console.log('2') // 3. 执行第一个 then
// 看到return ,需要将表达式执行完毕,才能执行外层第二个then
return (
new Promise((resolve, reject) => {
console.log('3') // 4. 构造函数执行
resolve()
})
// 5. 注册 then, 添加微任务
.then(() => {
console.log('4') // 6. 执行
})
.then(() => {
// 7. 注册
console.log('5') // 8. 执行
})
)
})
.then(() => {
// 9. 注册
console.log('6') // 10. 执行
})
注:
- Promise 状态不可逆
- 值穿透:前面的 then 没有添加回调,后面 then 的回调拿到 resolve 的参数值
- Promise.resolve('param') 直接从 pending 到 resolved,后面加 then 回调本身也会返回一个新的 promise 对象,所以也可以根据需要给 then 回调返回包含逻辑的 promise 对象,其 resolve(val)值可以作为后续的 then 的参数,前一个状态有后一个状态决定
- 任意一个非 promise 的值都会被包裹成 promise 对象,例如 return 2 会被包装为 return Promise.resolve(2)。
- catch 后的 then 中的 res 得到的就是 catch 中的返回值
// 任意一个非 promise 的值都会被包裹成 promise 对象,所以此处也被包裹成了return Promise.resolve(new Error('error!!!'))
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
- .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
const promise = Promise.resolve().then(() => {
return promise
})
promise.catch(console.err)
- .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传
// 第一个then和第二个then中传入的都不是函数,因此发生了透传,将resolve(1) 的值直接传到最后一个then里。
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)
Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。 .race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。 Promise.all().then()结果中数组的顺序和 Promise.all()接收到的数组顺序一致。 all 和 race 传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被 then 的第二个参数或者后面的 catch 捕获;但并不会影响数组中其它的异步任务的执行。
.finally
- .finally 会等 promise.then()执行完才会将 finally()加入微任务队列
- .finally 不管 Promise 对象最后的状态如何都会执行
- .finally()方法的回调函数不接受任何的参数
- .finally 返回的默认会是一个上一次的 Promise 对象值,不过如果抛出的是一个异常则返回异常的 Promise 对象。
- .finally 它的回调函数是接收不到 Promise 的结果的
Promise.resolve('1')
.then((res) => {
console.log(res)
})
.finally(() => {
console.log('finally')
})
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then((res) => {
console.log('finally2后面的then函数', res)
})
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中抛出的异常')
})
.then((res) => {
console.log('finally后面的then函数', res)
})
.catch((err) => {
console.log('捕获错误', err)
})
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve('resolve3')
console.log('timer1')
}, 0)
resolve('resovle1')
resolve('resolve2')
})
.then((res) => {
console.log(res)
setTimeout(() => {
// 打印的事finally的返回值,.finally的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值
console.log(p1)
}, 1000)
})
.finally((res) => {
console.log('finally', res)
})
Promise.all 和 Promise.race
- Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
- Promise.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
- Promise.all 和 Promise.race 传入的数组中如果有会抛出异常的异步任务,那么只有
最先抛出的错误会被捕获
,并且是被then 的第二个参数
或者后面的 catch 捕获
;但并不会影响数组中其它的异步任务的执行
。
function runAsync(x) {
const p = new Promise((r) =>
setTimeout(() => r(x, console.log(x)), 1000)
)
return p
}
async function async1() {
console.log('async1 start')
// 碰到了 await,它会阻塞 async1 后面代码的执行,因此会先去执行 async2 中的同步代码 async2,然后跳出 async1
// 可以理解为「紧跟着await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中」。
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
async1()
console.log('start')