关于 Promise 的讲解文章实在太多了,在此我就不写讲解了,直接实战检测自己对 Promise 的理解,下面是我做过的众多 Promise 题里面挑出来的 13 道题,我觉得容易错或者值得考究知识点的题,如果你想考察自己对 Promise 的掌握程度,可以做做看。
题目
题目 1
1 | const promise1 = new Promise((resolve, reject) => { |
分析:
- 先遇到
new Promise
,执行该构造函数中的代码输出promise1
- 遇到
resolve
函数, 将promise1
的状态改变为fulfilled
, 并将结果保存下来 - 碰到
promise1.then
这个微任务,返回的是Promise
的pending
状态,将它放入微任务队列 - 故
promise2
是一个新的状态为pending
的Promise
- 继续执行同步代码输出
promise1
的状态是fulfilled
,输出promise2
的状态是pending
- 宏任务执行完毕,查找微任务队列,发现
promise1.then
这个微任务且状态为fulfilled
,执行它。
输出结果:
1 | promise1 |
题目 2
1 | const promise = new Promise((resolve, reject) => { |
分析:
- 执行
new Promsise
,输出1
,setTimeout
宏任务加到宏任务队列,继续执行同步代码输出2
- 遇到
promise.then
,但其状态还是 pending,这里理解为先不执行;然后输出同步代码4
- 一轮循环过后,进入第二次宏任务,发现延迟队列中有
setTimeout
定时器,执行它 - 输出
timerStart
,遇到resolve
,将promise
的状态改为fulfilled
且保存结果并将之前的promise.then
推入微任务队列 - 继续执行同步代码
timerEnd
- 宏任务全部执行完毕,查找微任务队列,发现
promise.then
这个微任务,执行它。
输出结果:
1 | 1 |
题目 3
1 | const promise1 = new Promise((resolve, reject) => { |
分析:
- 从上至下,先执行第一个
new Promise
中的函数,碰到setTimeout
将它加入下一个宏任务列表 - 跳出
new Promise
,碰到promise1.then
这个微任务,但其状态还是为pending
,这里理解为先不执行;故promise2
是一个新的状态为pending
的Promise
- 执行同步代码
console.log('promise1')
,输出promise1
的状态为pending
- 执行同步代
console.log('promise2')
,输出promise2
的状态为pending
- 碰到第二个
setTimeout
,将其放入下一个宏任务列表 - 第一轮宏任务执行结束,并且没有微任务需要执行,因此执行第二轮宏任务
- 先执行第一个定时器里的内容,将
promise1
的状态改为fulfilled
且保存结果并将之前的promise1.then
推入微任务队列 - 该定时器中没有其它的同步代码可执行,因此执行本轮的微任务队列,也就是
promise1.then
,它抛出了一个错误,且将promise2
的状态设置为了rejected
- 第一个定时器执行完毕,开始执行第二个定时器中的内容
- 打印出
promise1
,且此时promise1
的状态为fulfilled
- 打印出
promise2
,且此时promise2
的状态为rejected
1 | promise1 Promise {<pending>} |
题目 4
1 | const promise = new Promise((resolve, reject) => { |
分析:
catch
不管被连接到哪里,都能捕获上层未捕捉过的错误。- 由于
catch()
也会返回一个Promise
,且由于这个Promise
没有返回值,所以打印出来的是undefined
。
输出结果:
1 | catch: error |
题目 5
1 | Promise.resolve() |
分析:
- 在
Promise
中,返回任意一个非promise
的值都会被包裹成promise
对象,例如return new Error('error!!!')
会被包装为return Promise.resolve(new Error('error!!!'))
.then
或者.catch
中return
一个error
对象并不会抛出错误,所以不会被后续的.catch
捕获。
当然如果你抛出一个错误的话,可以用下面任意一种:
1 | return Promise.reject(new Error('error!!!')) |
输出结果:
1 | then: Error: error!!! |
题目 6
1 | const promise = Promise.resolve().then(() => { |
分析:
.then
或.catch
返回的值不能是promise
本身,否则会造成死循环。
输出结果:
1 | Promise {<rejected>: TypeError: Chaining cycle detected for promise #<Promise>} |
题目 7
1 | Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log) |
分析:
.then
或者.catch
的参数期望是函数,传入非函数则会发生值透传。- 第一个
then
和第二个then
中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1)
的值直接传到最后一个then
里。
输出结果:
1 | 1 |
题目 8
1 | Promise.reject('err!!!') |
分析:
.then
函数的第一个参数是用来处理Promise
成功的函数,第二个则是处理失败的函数。Promise.resolve('err!!!')
的值会进入成功的函数,Promise.reject('err!!!')
的值会进入失败的函数。- 如果去掉第二个参数,就会进入
catch()
中
输出结果:
1 | 'error' 'error!!!' |
题目 9
1 | Promise.resolve('1') |
分析:
.finally()
方法不管Promise
对象最后的状态如何都会执行- 它最终返回的默认会是一个上一次的
Promise
对象值,不过如果抛出的是一个异常则返回异常的Promise
对象。 - 第一行代码遇到
Promise.resolve('1')
,再把.then()
加入微任务,这时候要注意,代码并不会接着往链式调用的下面走,也就是不会先将.finally
加入微任务列表,那是因为.then
本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管.finally()
- 执行
Promise.resolve('2')
,把.finally
加入微任务队列,且链式调用后面的内容得等该任务执行完后才执行 - 本轮宏任务执行完,执行微任务列表第一个微任务输出
1
,遇到.finally()
,将它加入微任务列表(第三个)待执行;再执行第二个微任务输出finally2
,遇到.then
,把它加入到微任务(第四个)列表待执行 - 本轮执行完两个微任务后,检索微任务列表发现还有两个微任务,故执行,输出
finally
和finally2后面的then函数 2
输出结果:
1 | 1 |
题目 10
1 | async function async1() { |
分析:
- 在
async1
中await
后面的Promise
是没有返回值的,也就是它的初始状态是pending
状态,因此相当于一直在await
- 所以在
await
之后的内容是不会执行的,也包括async1
后面的.then
。
输出结果:
1 | srcipt start |
题目 11
1 | async function async1() { |
分析:
await
后面跟着的是一个状态为rejected
的promise
。- 如果在
async
函数中抛出了错误,则终止错误结果,不会继续向下执行。
输出结果:
1 | async2 |
题目 12
1 | var p1 = new Promise(function (resolve, reject) { |
分析:
Promise 中的异常由 then 参数中第二个回调函数(Promise 执行失败的回调)处理,异常信息将作为 Promise 的值。异常一旦得到处理,then 返回的后续 Promise 对象将恢复正常,并会被 Promise 执行成功的回调函数处理。另外,需要注意 p1、p2 多级 then 的回调函数是交替执行的 ,这正是由 Promise then 回调的异步性决定的。
输出结果:
1 | p1 then err: ReferenceError: foo is not defined |
题目 13
1 | var p1 = new Promise(function (resolve, reject) { |
分析:
Promise 回调函数中的第一个参数 resolve,会对 Promise 执行”拆箱”动作。即当 resolve 的参数是一个 Promise 对象时,resolve 会”拆箱”获取这个 Promise 对象的状态和值,但这个过程是异步的。p1”拆箱”后,获取到 Promise 对象的状态是 resolved,因此 fulfilled 回调被执行;p2”拆箱”后,获取到 Promise 对象的状态是 rejected,因此 rejected 回调被执行。但 Promise 回调函数中的第二个参数 reject 不具备”拆箱“的能力,reject 的参数会直接传递给 then 方法中的 rejected 回调。因此,即使 p3 reject 接收了一个 resolved 状态的 Promise,then 方法中被调用的依然是 rejected,并且参数就是 reject 接收到的 Promise 对象。
输出结果:
1 | p3 rejected: [object Promise] |
特别鸣谢
这些题是我从下面两个链接的题目又选出来的,仅作为自己的错题集/笔记题来记录,方便以后我自己检验回顾。如果你想更全面学,建议直接去做下面两篇的题。
ps: 个人技术博文 Github 仓库,觉得不错的话欢迎 star,给我一点鼓励继续写作吧~