手写Promise
这一节咱们一起来手写Promise
这一章咱们会学习的有:
- 实现Promise的核心功能:
Promise:
- 实例方法:
catch,finally
- 静态方法:
resolve,reject,race,all,allSettled,any
Promise\A+标准,并跑通872个单元测试
首先明确Promise的核心用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const p = new HMPromise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000); })
p.then(res => { console.log('res:', res) return 'success2' }, err => { console.log('err:', err) }).then(res2 => { console.log('res2:', res2) })
|
手写Promise-构造函数
需求:
- 实现
HMPromise类,可以用如下的方式实例化
- 实例化时传入回调函数
- 回调函数立刻执行
- 回调函数接收函数
resolve和reject
1 2 3 4 5
| const p = new HMPromise((resolve, reject) => { resolve('success') })
|
核心步骤:
- 定义类
HMPromise
- 添加构造函数
constructor
- 定义resolve/reject
- 执行回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class HMPromise { constructor(func) { const resolve = (result) => { console.log('resolve-执行啦:', result) } const reject = (result) => { console.log('reject-执行啦:', result) }
func(resolve, reject) } }
|
面试回答:
手写Promise-构造函数
- 定义类
HMPromise,内部添加构造函数constructor,构造函数需要接收回调函数func
- 在构造函数中定义
resolve和reject
- 构造函数内部调用
func并将resolve和reject传入:func(resolve,reject)
手写Promise-状态、成功or失败原因
需求:
HMPromise增加state属性,只能是如下3个值
pending:待定,默认状态
fulfilled:已兑现,操作成功
rejected:已拒绝,操作失败
HMPromise增加result属性,记录成功/失败原因
- 调用
resolve或reject,修改状态,并记录成功/失败原因
1 2 3 4 5 6
| const p = new HMPromise((resolve, reject) => { resolve('success') }) p.state p.result
|
核心步骤:
- 定义常量保存状态,避免硬编码
HMPromise中定义
- 属性:
state保存状态,result成功/失败原因
- 修改
state的私有方法,修改状态并记录result
- 注意:
state只有在pending时,才可以修改,且不可逆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result } }
func(resolve, reject) } }
|
面试回答:
手写Promise-状态、成功or失败原因
- 定义3个常量用来保存状态,
pending,fulfilled,rejected
HMPromise内部定义属性state和result分别用来保存状态和原因
- 调用
resolve时传入具体原因,如果状态为pending则更改状态并记录兑现原因
- 调用
reject时传入具体原因,如果状态为pending则更改状态并记录拒绝原因
手写Promise-then方法-成功和失败回调
需求:
- then方法的回调函数1: 状态变为
fulfilled时触发,并获取成功结果
- then方法的回调函数2: 状态变为
rejected时触发,并获取失败原因
- then方法的回调函数1或2没有传递的特殊情况处理,参考:then方法的参数
1 2 3 4 5 6 7 8 9
| const p = new HMPromise((resolve, reject) => { resolve('success') }) p.then(res => { console.log('成功回调:', res) }, err => { console.log('失败回调:', err) })
|
核心步骤:
- 增加
then方法,根据不同的状态执行对应的回调函数,并传入result
- 参数1:成功的回调函数
- 参数2:失败的回调函数
- 判断参数
- 没有传递
onFulfilled,onRejected时
- 设置默认值(参考文档)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
if (this.state === FULFILLED) { onFulfilled(this.result) } else if (this.state === REJECTED) { onRejected(this.result) } } }
|
面试回答
手写Promise-then方法-成功和失败回调
- 添加
then方法,接收2个回调函数:
- 成功回调
onFulfilled
- 失败回调
onRejected
- 判断传入的
onFulfilled和onRejected是否为函数,如果不是设置默认值
- 根据状态调用
onFulfilled或onRejected并传入兑现或拒绝原因
手写Promise-then方法-异步和多次调用
需求:
- 实例化传入的回调函数,内部支持异步操作
then方法支持多次调用(非链式编程)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p = new HMPromise((resolve, reject) => { setTimeout(() => { resolve('success') }, 2000); }) p.then(res => { console.log('then1:', res) }, err => { console.log('then1:', err) }) p.then(res => { console.log('then2:', res) }, err => { console.log('then2:', err) })
|
核心步骤:
- 定义属性:保存传入的回调函数:
[]
- 保存回调函数:状态为
pending时
- 调用成功回调:
resolve内部
- 调用失败回调:
reject内部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
if (this.state === FULFILLED) { onFulfilled(this.result) } else if (this.state === REJECTED) { onRejected(this.result) } else if (this.state === PENDING) { this.#handlers.push({ onFulfilled, onRejected }) } } }
|
面试回答
手写Promise-then方法-异步和多次调用
HMPromise添加私有属性#handlers
- 保存then方法调用时状态为
pending的回调函数
- 格式为对象数组:
[{onFulfilled,onRejected}...]
构造函数内部调整resolve和reject的逻辑:
- 调用
resolve时取出数组#handlers中的所有onFulfilled回调函数进行调用
- 调用
reject时取出数组#handlers中的所有onRejected回调函数进行调用
手写Promise-异步任务-api补充
- 传送门:MDN-queueMicrotask
- 传送门:MDN-queueMicrotask使用指南
- 传送门:MDN-setImmediate
- 传送门:MDN-MutationObserver
需求:
- 如下代码打印结果为
1,2,4,3
- 核心: 让then方法的回调函数以异步任务的方式执行
1 2 3 4 5 6 7 8
| console.log('top') const p = new HMPromise((resolve, reject) => { resolve('success') }) p.then(res => { console.log(res) }) console.log('bottom')
|
这里我们参考vue2的做法:
- vue2:Promise.then、MutationObserver 、 setImmediate 、 setTimeout
- 我们选用:queueMicrotask 、MutationObserver 、 setTimeout
选用原因:
Promise.then: 我们是手写Promise,故不选用这个
queueMicrotask :新式浏览器均支持,node11开始支持,ie不支持
MutationObserver :新式浏览器均支持,ie11开始支持
setImmediate: 新式浏览器只有edge支持,ie10开始支持
setTimeout:浏览器支持,node支持
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
queueMicrotask(() => { })
console.log('top')
const obs = new MutationObserver(() => { console.log('MutationObserver-run') })
const divNode = document.createElement('div')
obs.observe(divNode, { childList: true })
divNode.innerText = 'itheima 666' console.log('bottom')
|
面试回答
请问可以使用哪些方式开启异步任务:
Promise.then: 我们是手写Promise,故不选用这个
queueMicrotask :新式浏览器均支持,node11开始支持,ie不支持
MutationObserver :新式浏览器均支持,ie11开始支持
setImmediate: 新式浏览器只有edge支持,ie10开始支持
setTimeout:浏览器支持,node支持
手写Promise-异步任务-函数封装
需求:
- 封装函数
runMicrotask内部执行异步任务
- 使用
runMicrotask让then方法的回调函数为异步任务
核心步骤:
- 封装函数
runMicrotask并传入回调函数
- 内部依次判断:
queueMicrotask 、MutationObserver 、 setTimeout并使用即可
- 使用
runMicrotask开启执行异步任务即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| function runAsynctask(callback) { if (typeof queueMicrotask === 'function') { queueMicrotask(callback) } else if (typeof MutationObserver === 'function') { const obs = new MutationObserver(callback) const divNode = document.createElement('div') obs.observe(divNode, { childList: true }) divNode.innerText = 'itheima666' } else { setTimeout(callback, 0) } }
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
if (this.state === FULFILLED) { runAsynctask(() => { onFulfilled(this.result) }) } else if (this.state === REJECTED) { runAsynctask(() => { onRejected(this.result) }) } else if (this.state === PENDING) { this.#handlers.push({ onFulfilled: () => { runAsynctask(() => { onFulfilled(this.result) }) }, onRejected: () => { runAsynctask(() => { onRejected(this.result) }) } }) } } }
|
面试回答
手写Promise-异步任务-函数封装
封装执行异步任务的函数
定义函数传入异步任务(回调函数)
内部根据实际情况判断并使用开启异步任务的api即可,比如queueMicrotask,MutationObserver
调用的api可以根据实际情况进行调整
如果都无法执行,使用setTimeout兜底
调整then中的逻辑,fulFilled,rejected,pending3种状态时的回调函数,使用封装的函数包装一次
手写Promise-链式编程-fulfilled状态-返回值+异常
需求:
then的链式编程
- 目前只考虑
then的第一个回调函数
- 返回普通值
- 内部出现异常
1 2 3 4 5 6 7 8 9 10 11 12 13
| const p = new HMPromise((resolve, reject) => { resolve(1) }) p.then(res => { console.log(res) return 2 }).then(res => { console.log(res) }, err => { console.log(err) })
|
核心步骤:
- 调整
then方法,返回一个新的HMPromise对象
- 使用
try-catch捕获异常,并通过reject传递
- 内部获取
onFulfilled的执行结果,并通过resolve传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| function runAsynctask(callback) { if (typeof queueMicrotask === 'function') { queueMicrotask(callback) } else if (typeof MutationObserver === 'function') { const obs = new MutationObserver(callback) const divNode = document.createElement('div') obs.observe(divNode, { childList: true }) divNode.innerText = 'itheima666' } else { setTimeout(callback, 0) } }
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
const p2 = new HMPromise((resolve, reject) => { if (this.state === FULFILLED) { runAsynctask(() => { try { const x = onFulfilled(this.result) resolve(x) } catch (error) { reject(error) } }) } else if (this.state === REJECTED) { runAsynctask(() => { onRejected(this.result) }) } else if (this.state === PENDING) { this.#handlers.push({ onFulfilled: () => { runAsynctask(() => { onFulfilled(this.result) }) }, onRejected: () => { runAsynctask(() => { onRejected(this.result) }) } }) }
})
return p2 } }
|
面试回答:
手写Promise-链式编程-fulfilled状态-返回值+异常,本节只考虑
- 链式编程的本质then方法会返回一个新的
HMPromise对象
- 将原本的代码迁移到返回的
HMPromise对象的回调函数中
- 内部通过
try-catch捕获异常,出现异常通过reject传递异常
- 获取
onFulfilled的执行结果,并通过resolve传递
手写Promise-链式编程-fulfilled状态-返回Promise
需求:
then的链式编程
- 目前只考虑
then的第一个回调函数
- 返回
Promise
1 2 3 4 5 6 7 8 9 10 11 12 13
| const p = new HMPromise((resolve, reject) => { resolve(1) }) p.then(res => { return new HMPromise((resolve, reject) => { resolve(2) }) }).then(res => { console.log('p2:', res) }, err => { console.log('p2:', err) })
|
核心步骤:
- 判断是否为
HMPromise实例
- 调用
then方法依次传入回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| function runAsynctask(callback) { if (typeof queueMicrotask === 'function') { queueMicrotask(callback) } else if (typeof MutationObserver === 'function') { const obs = new MutationObserver(callback) const divNode = document.createElement('div') obs.observe(divNode, { childList: true }) divNode.innerText = 'itheima666' } else { setTimeout(callback, 0) } }
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
const p2 = new HMPromise((resolve, reject) => { if (this.state === FULFILLED) { runAsynctask(() => { try { const x = onFulfilled(this.result) if (x instanceof HMPromise) { x.then(res => resolve(res), err => reject(err)) } else { resolve(x) } } catch (error) { reject(error) } }) } else if (this.state === REJECTED) { runAsynctask(() => { onRejected(this.result) }) } else if (this.state === PENDING) { this.#handlers.push({ onFulfilled: () => { runAsynctask(() => { onFulfilled(this.result) }) }, onRejected: () => { runAsynctask(() => { onRejected(this.result) }) } }) } }) return p2 } }
|
面试回答
手写Promise-链式编程-fulfilled状态-返回Promise
- 判断
onFulfilled的执行结果是否为HMPromise实例
- 如果是的话调用返回值的
then方法,获取兑现和拒绝的原因并通过resolve和reject传递即可
手写Promise-链式编程-fulfilled状态-重复引用
需求:
then中返回的then方法返回的Promise实例报错
- 注:下列代码中的
p2

1 2 3 4 5 6 7 8 9
| const p = new HMPromise((resolve, reject) => { resolve(1) }) const p2 = p.then(res => { return p2 }) p2.then( res => { }, err => console.log('err:', err))
|
核心步骤:
- 判断是否相同,抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| function runAsynctask(callback) { if (typeof queueMicrotask === 'function') { queueMicrotask(callback) } else if (typeof MutationObserver === 'function') { const obs = new MutationObserver(callback) const divNode = document.createElement('div') obs.observe(divNode, { childList: true }) divNode.innerText = 'itheima666' } else { setTimeout(callback, 0) } }
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
const p2 = new HMPromise((resolve, reject) => { if (this.state === FULFILLED) { runAsynctask(() => { try { const x = onFulfilled(this.result) if (x === p2) { throw new TypeError('Chaining cycle detected for promise #<Promise>') } if (x instanceof HMPromise) { x.then(res => resolve(res), err => reject(err)) } else { resolve(x) } } catch (error) { reject(error) } }) } else if (this.state === REJECTED) { runAsynctask(() => { onRejected(this.result) }) } else if (this.state === PENDING) { this.#handlers.push({ onFulfilled: () => { runAsynctask(() => { onFulfilled(this.result) }) }, onRejected: () => { runAsynctask(() => { onRejected(this.result) }) } }) } }) return p2 } }
|
面试回答
手写Promise-链式编程-fulfilled状态-重复引用
- 判断
onFulfilled函数的返回值是否和then方法内部返回的HMPromise相同
- 如果相同抛出错误
new TypeError('Chaining cycle detected for promise #<Promise>')
手写Promise-链式编程-rejected状态
需求:
then的第二个回调函数,执行reject时的链式编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const p = new HMPromise((resolve, reject) => { reject(1) }) const p2 = p.then(undefined, err => { throw 'error' }) p2.then(res => { console.log('p2-res:', res) }, err => { console.log('p2-err:', err) })
|
核心步骤:
- 处理异常:
onRejected的异常
- 获取返回值:
onRejected的返回值
- 将
fulfilled状态中的处理逻辑抽取为函数resolvePromise并复用
fulfilled和rejected状态中调用函数resolvePromise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| function runAsynctask(callback) { if (typeof queueMicrotask === 'function') { queueMicrotask(callback) } else if (typeof MutationObserver === 'function') { const obs = new MutationObserver(callback) const divNode = document.createElement('div') obs.observe(divNode, { childList: true }) divNode.innerText = 'itheima666' } else { setTimeout(callback, 0) } }
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
const p2 = new HMPromise((resolve, reject) => { if (this.state === FULFILLED) { runAsynctask(() => { try { const x = onFulfilled(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }
else if (this.state === REJECTED) { runAsynctask(() => { try { const x = onRejected(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }
else if (this.state === PENDING) { this.#handlers.push({ onFulfilled: () => { runAsynctask(() => { onFulfilled(this.result) }) }, onRejected: () => { runAsynctask(() => { onRejected(this.result) }) } }) } })
return p2 } }
function resolvePromise(p2, x, resolve, reject) { if (x === p2) { throw new TypeError('Chaining cycle detected for promise #<Promise>') } if (x instanceof HMPromise) { x.then(res => resolve(res), err => reject(err)) } else { resolve(x) } }
|
面试回答
手写Promise-链式编程-rejected状态
- 判断
onRejected可能出现的异常,如果出现通过reject传递
- 获取
onRejected函数的执行结果
- 将
fulfilled状态时的处理逻辑抽取为函数,rejected状态时调用函数复用逻辑
手写Promise-链式编程-pending状态
需求:
- 执行异步操作时,支持链式编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const p = new HMPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 2000) }) const p2 = p.then(res => { throw 'error' }) p2.then(res => { console.log('p2-res:', res) }, err => { console.log('p2-err:', err) })
|
核心步骤:
处理异常:
fulfilled状态时推入回调函数数组时增加try-catch
获取返回值:
- 推入数组时,增加获取返回值的操作
调用上一节封装的函数resolvePromise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| function runAsynctask(callback) { if (typeof queueMicrotask === 'function') { queueMicrotask(callback) } else if (typeof MutationObserver === 'function') { const obs = new MutationObserver(callback) const divNode = document.createElement('div') obs.observe(divNode, { childList: true }) divNode.innerText = 'itheima666' } else { setTimeout(callback, 0) } }
function resolvePromise(p2, x, resolve, reject) { if (x === p2) { throw new TypeError('Chaining cycle detected for promise #<Promise>') } if (x instanceof HMPromise) { x.then(res => resolve(res), err => reject(err)) } else { resolve(x) } }
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } } const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } } func(resolve, reject) }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
const p2 = new HMPromise((resolve, reject) => { if (this.state === FULFILLED) { runAsynctask(() => { try { const x = onFulfilled(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }
else if (this.state === REJECTED) { runAsynctask(() => { try { const x = onRejected(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }
else if (this.state === PENDING) { this.#handlers.push({ onFulfilled: () => { runAsynctask(() => { try { const x = onFulfilled(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }, onRejected: () => { runAsynctask(() => { try { const x = onRejected(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) } }) } })
return p2 } }
|
面试回答:
手写Promise-链式编程-pending状态
then方法中pending状态时推入数组的函数增加try-catch捕获异常
- 获取推入数组的回调函数的返回值
- 调用上一节封装的函数并传入获取的值
小结:
到目前已经将核心功能全部实现啦,接下来开始实现后续功能
- [x] 实现Promise的核心功能:
- [ ] Promise:
- 实例方法:
- 静态方法:
- [ ]
resolve
- [ ]
reject
- [ ]
race
- [ ]
all
- [ ]
allSettled
- [ ]
any
- [ ] Promise\A+标准,并跑通872个单元测试
手写Promise-实例方法catch
需求:
- 实现实例方法
catch,可以实现如下调用
1 2 3 4 5 6 7 8 9 10
| const p = new HMPromise((resolve, reject) => { reject('reject-error') }) p.then(res => { console.log('res:', res) }).catch(err => { console.log('err:', err) })
|
核心步骤:
- 参考文档,catch等同于:
then(undefined,onRjected)
- 直接添加
catch方法,内部调用then
- 使用
try-catch包裹constructor中的func捕获异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| function runAsynctask(callback) { if (typeof queueMicrotask === 'function') { queueMicrotask(callback) } else if (typeof MutationObserver === 'function') { const obs = new MutationObserver(callback) const divNode = document.createElement('div') obs.observe(divNode, { childList: true }) divNode.innerText = 'itheima666' } else { setTimeout(callback, 0) } }
function resolvePromise(p2, x, resolve, reject) { if (x === p2) { throw new TypeError('Chaining cycle detected for promise #<Promise>') } if (x instanceof HMPromise) { x.then(res => resolve(res), err => reject(err)) } else { resolve(x) } }
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class HMPromise { state = PENDING result = undefined #handlers = []
constructor(func) { const resolve = (result) => { if (this.state === PENDING) { this.state = FULFILLED this.result = result this.#handlers.forEach(({ onFulfilled }) => { onFulfilled(this.result) }) } }
const reject = (result) => { if (this.state === PENDING) { this.state = REJECTED this.result = result this.#handlers.forEach(({ onRejected }) => { onRejected(this.result) }) } }
try { func(resolve, reject) } catch (error) { reject(error) } }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
const p2 = new HMPromise((resolve, reject) => { if (this.state === FULFILLED) { runAsynctask(() => { try { const x = onFulfilled(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }
else if (this.state === REJECTED) { runAsynctask(() => { try { const x = onRejected(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }
else if (this.state === PENDING) { this.#handlers.push({ onFulfilled: () => { runAsynctask(() => { try { const x = onFulfilled(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) }, onRejected: () => { runAsynctask(() => { try { const x = onRejected(this.result) resolvePromise(p2, x, resolve, reject) } catch (error) { reject(error) } }) } }) } })
return p2 }
catch(onRejected) { return this.then(undefined, onRejected) } }
|
面试回答
手写Promise-实例方法catch
定义catch方法,接收拒绝的回调函数onRejected
catch方法的本质是内部调用then方法
调用形式为第一个回调函数传入undefined,第二个回调函数传入onRejected即可
手写Promise-实例方法finally
需求:
- 无论成功失败都会执行
finally的回调函数
- 回调函数不接受任何参数
1 2 3 4 5 6 7 8 9 10 11 12
| const p = new HMPromise((resolve, reject) => { }) p.then(res => { console.log('res:', res) }).catch(err => { console.log('err:', err) }).finally(() => { console.log('finally') })
|
核心步骤:
- 参考文档:finally方法类似于调用
then(onFinally,onFinally),且不接受任何回调函数
- 注意:
- 版面问题,这里只保留函数部分的逻辑,其他代码未改动
- 后续未特殊说明,只保留函数部分代码
1 2 3
| finally(onFinally) { return this.then(onFinally,onFinally) }
|
面试回答:
手写Promise-实例方法finally
- 添加
finally方法,接收最终执行的回调函数onFinally
finally方法的本质为内部调用then方法
- 调用形式为第一个和第二个回调函数均传入
onFinally即可
1 2 3
| finally(onFinally) { return this.then(onFinally,onFinally) }
|
到目前已经将实例方法都实现啦
- [x] 实现Promise的核心功能:
- [x] Promise:
- 实例方法:
- 静态方法:
- [ ]
resolve
- [ ]
reject
- [ ]
race
- [ ]
all
- [ ]
allSettled
- [ ]
any
- [ ] Promise\A+标准,并跑通872个单元测试
手写Promise-静态方法resolve
需求:
- 返回一个带有成功原因的
Promise对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| HMPromise.resolve(new HMPromise((resolve, reject) => { })).then(res => { console.log('res:', res) }, err => { console.log('err:', err) }) HMPromise.resolve('itheima').then(res => { console.log(res) })
|
核心步骤:
- 增加静态方法
resolve,根据传入的值返回不同的结果即可
1 2 3 4 5 6 7 8 9 10 11 12
| static resolve(value) { if (value instanceof HMPromise) { return value } return new HMPromise((resolve) => { resolve(value) }) }
|
面试回答:
手写Promise-静态方法resolve
通过static关键字添加静态方法resolve,接收参数value
内部判断传入的值
- 如果是
Promise实例,直接返回
- 其他的值,创建
Promise实例并返回,内部通过resolve(value)传递value
手写Promise-静态方法reject
需求:
- 返回一个带有拒绝原因的
Promise对象
1 2 3
| HMPromise.reject('error').catch(res => { console.log(res) })
|
核心步骤:
- 添加静态方法
- 返回
rejected状态的Promise
1 2 3 4 5 6 7
| static reject(value) { return new HMPromise((undefined, reject) => { reject(value) }) }
|
面试回答
手写Promise-静态方法reject
- 添加静态方法
reject并接收参数value
- 内部返回一个拒绝状态的
Promise实例即可
手写Promise-静态方法race
需求:
- 接收Promise数组,数组中第一个Promise敲定时,获取
成功/失败结果
- 传入的参数不是数组,直接报错

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const p1 = new HMPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 2000) }) const p2 = new HMPromise((resolve, reject) => { setTimeout(() => { reject(2) }, 1000) }) HMPromise.race([p1, p2, 'itheima']).then((res) => { console.log('res:', res) }, err => { console.log('err:', err) })
|
核心步骤:
- 返回
Promise
- 判断是否未数组,不是直接报错
- 等待第一个敲定
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static race(promises) { return new HMPromise((resolve, reject) => { if (!Array.isArray(promises)) { return reject(new TypeError('Argument is not iterable')) } promises.forEach(p => { HMPromise.resolve(p).then(res => { resolve(res) }, err => { reject(err) }) }) }) }
|
面试回答:
手写Promise-静态方法race
- 添加静态方法
race接收参数promises
- 内部返回一个新的
Promise实例,在返回的Promise实例中:
- 判断参数是否为数组,不是通过
reject传递错误
- 遍历Promise数组,通过
resolve静态方法等待每一个兑现
- 任何一个兑现,调用
resolve传递兑现结果
- 任何一个拒绝,调用
reject传递拒绝原因
手写Promise-静态方法all
需求:
- 接收Promise数组
- 所有Promise都成功时,返回一个成功的Promise对象及成功数组
- 任何一个Promise失败,返回一个失败的Promise对象及第一个失败原因
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const p1 = HMPromise.resolve(1) const p2 = new HMPromise((resolve, reject) => { setTimeout(() => { resolve(2) }, 1000) }) const p3 = 3 HMPromise.all([p1, p2, p3]).then(res => { console.log('res:', res) }, err => { console.log('err:', err) })
|
核心步骤:
返回Promise
判断参数是否未数组:
- 不是:直接报错
- 是:
- 空数组直接兑现
- 处理全部兑现:记录结果->判断全部兑现
- 处理第一个拒绝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| static all(promises) { return new HMPromise((resolve, reject) => { if (!Array.isArray(promises)) { return reject(new TypeError('Argument is not iterable')) } promises.length === 0 && resolve(promises) const results = [] let count = 0 promises.forEach((p, index) => { HMPromise.resolve(p).then(res => { results[index] = res count++ count === promises.length && resolve(results) }, err => { reject(err) }) }) }) }
|
面试回答:
手写Promise-静态方法all
- 添加静态方法
all
- 内部返回
Promise实例,在返回的Promise实例中:
- 判断参数是否为数组,不是通过
reject传递错误
- 空数组直接以空数组为结果进行兑现
- 遍历
Promise数组,通过resolve静态方法等待结果
- 处理全部兑现:
- 通过数组记录结果,用索引的方式来添加,目的是保证结果的顺序和
Promise数组的顺序一致
- 通过兑现次数进行判断,因为是通过索引的方式记录结果,如果第一次兑现的是最后一个,那么数组的长度就已经和Promise数组的长度一致了,所以需要通过兑现次数来进行判断
- 任意一个拒绝,调用
reject传递拒绝原因
手写Promise-静态方法allSettled
需求:-传送门
- 传入
Promise数组,当所有对象都已敲定时
- 返回一个新的
Promise对象及以数组形式保存的结果
1 2 3 4 5 6 7 8 9 10 11 12
| const p1 = HMPromise.resolve(1) const p2 = 2 const p3 = new HMPromise((resolve, reject) => { setTimeout(() => { reject(3) }, 1000) }) HMPromise.allSettled([p1, p2, p3]).then(res => { console.log('res:', res) }, err => { console.log('err:', err) })
|
核心步骤:
- 返回
Promise
- 判断是否为数组:
- 不是:报错
- 是:
- 空数组:直接兑现
- 等待全部敲定:并记录结果
- 处理兑现:
{state:FULFILLED,value:'xxx'}
- 处理拒绝:
{state:REJECTED,reason:'xxx'}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| static allSettled(promises) { return new HMPromise((resolve, reject) => { if (!Array.isArray(promises)) { return reject(new TypeError('Argument is not iterable')) } promises.length === 0 && resolve(promises)
const results = [] let count = 0 promises.forEach((p, index) => { HMPromise.resolve(p).then(res => { results[index] = { status: FULFILLED, value: res } count++ count === promises.length && resolve(results) }, err => { results[index] = { status: REJECTED, reason: err } count++ count === promises.length && resolve(results) }) }) }) }
|
面试回答:
手写Promise-静态方法allSettled
做法和all方法类似,区别是要获取全部敲定的结果(成功/拒绝),以及获取的结果是对象形式
- 添加静态方法
allSettled
- 内部返回
Promise实例,在返回的Promise实例中:
- 判断参数是否为数组,不是通过
reject传递错误
- 空数组直接以空数组为结果进行兑现
遍历Promise数组,通过resolve静态方法等待敲定结果
等待全部敲定:并记录结果,根据兑现和拒绝将如下格式的内容通过索引的方式的记录到数组中
处理兑现:{state:FULFILLED,value:'xxx'}
处理拒绝:{state:REJECTED,reason:'xxx'}
根据敲定的次数判断是否全部敲定,全部敲定之后,通过resolve传递结果数组
手写Promise-静态方法any
需求:-传送门
- 传入
Promise数组,
- 任何一个
Promise对象敲定时,返回一个新的Promise对象,及对应的结果
- 所有Promise都被拒绝时,返回一个包含所有拒绝原因的
AggregateError错误数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const p1 = new HMPromise((resolve, reject) => { setTimeout(() => { reject(1) }, 2000) }) const p2 = 2 const p3 = new HMPromise((resolve, reject) => { setTimeout(() => { resolve(3) }, 1000) }) HMPromise.any([p1, p2, p3]).then(res => { console.log('res:', res) }, err => { console.dir(err) })
|
核心步骤:
- 返回Promise
- 判断是否为数组
- 不是:报错
- 是:
- 空数组:直接拒绝
- 等待结果:
- 第一个兑现
- 全部拒绝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| static any(promises) { return new HMPromise((resolve, reject) => { if (!Array.isArray(promises)) { return reject(new TypeError('Argument is not iterable')) } promises.length === 0 && reject(new AggregateError(promises, 'All promises were rejected'))
const errors = [] let count = 0 promises.forEach((p, index) => { HMPromise.resolve(p).then(res => { resolve(res) }, err => { errors[index] = err count++ count === promises.length && reject(new AggregateError(errors, 'All promises were rejected')) }) }) }) }
|
面试回答:
手写Promise-静态方法any
做法和all方法也有点类似,区别是获取第一个兑现,或者是全部拒绝
- 添加静态方法
any
- 内部返回
Promise实例,在返回的Promise实例中:
- 判断参数是否为数组,不是通过
reject传递错误
- 空数组直接以空数组为结果进行兑现
- 遍历
Promise数组,通过resolve静态方法等待结果
- 第一个兑现,通过
resolve传递兑现结果
- 全部拒绝:
- 定义数组,保存拒绝原因,通过索引记录,目的是保证顺序和
Promise数组一致
- 通过次数判断是否全部拒绝,当全部拒绝时,通过
reject传递AggregateError类型的错误,并将拒绝原因数组传递进去即可
到目前已经将静态方法都实现啦
手写Promise-Promise\A+测试
接下来咱们来测试一下手写Promise的代码能否通过Promise\A+测试
Promise\A+规范:
Promise\A+是社区推出的规范,其实最早Promise也是社区推出并实现的,旨在规范Promise的实现,里面约定了:
- 状态必须是:pending,fulfilled,rejected
- then方法的详细实现细节
- ….
早期使用Promise可能需要导入一些库,比如:
现在已经不需要了,因为在ES6中已经加入语言标准,我们可以直接使用了:从 Chrome 32、Opera 19、Firefox 29、Safari 8 和 Microsoft Edge 开始,promises 是默认开启的。若要使缺乏完整 promise 实现的浏览器符合规范,或将 promise 添加到其他浏览器和 Node.js 中,请查看polyfill (2k gzipped)。
上面提到的库,以及ES6中实现的Promise,还有我们手写的Promise其实都是按照标准进行编写的,那么如何测试是否符合标准呢
测试:
社区提供了promises-aplus-tests用来测试实现的Promise是否符合规范,使用方式为:
使用CommonJS的方式暴露对象,要求如下
1 2 3 4
| 1. 提供deferred方法,返回对象{promise,resolve,reject} 1.1 promise: pending状态的promise实例(自己手写的Promise) 1.2 resolve: 以传入的原因兑现promise 1.3 reject: 以传入的原因拒绝promise
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| module.exports = { deferred() { const res = {} res.promise = new HMPromise((resolve, reject) => { res.resolve = resolve res.reject = reject }) return res } }
|
下包:
初始化项目: npm init -y
下包:npm i promises-aplus-tests -D
配置并执行命令:
package.json的scripts中加入
注: HMPromise是文件名,根据实际情况调整自己的文件名即可
1
| "test": "promises-aplus-tests HMPromise"
|
执行命令:npm run test
测试:
我们目前的写法中,没有考虑所有的边界情况,测试时会在2.3.3开始出错

只需要将resolvePromise函数替换为如下写法即可:
- 函数名,参数顺序和原函数一致
- 函数内部使用,序号+说明的方式对Promise\A+的标准进行标注
大伙可以参考注释对比确认还需要考虑哪些便捷情况,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| function resolvePromise(p2, x, resolve, reject) { if (x === p2) { throw new TypeError('Chaining cycle detected for promise'); }
if (x instanceof HMPromise) { x.then(y => { resolvePromise(p2, y, resolve, reject) }, reject); } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) { try { var then = x.then; } catch (e) { return reject(e); }
if (typeof then === 'function') { let called = false; try { then.call( x, y => { if (called) return; called = true; resolvePromise(p2, y, resolve, reject); }, r => { if (called) return; called = true; reject(r); } ) } catch (e) { if (called) return; called = true;
reject(e); } } else { resolve(x); } } else { return resolve(x); } }
|
替换完毕之后,再次执行npm run test,全部测试通过.

面试回答:
手写Promise-Promise\A+测试
Promise和Promise\A+规范的关系
Promise\A+是社区推出的规范,最早Promise也是社区推出并实现的,旨在规范Promise的实现,里面约定了:
- 状态必须是
pending,fulfilled,rejected
then方法的详细实现细节
- 各种边界情况….
早期使用Promise需要导入第三方库,现在在新式浏览器中已经不需要导入第三方库,因为Promise是默认开启的
- 无论是早期实现了Promise的第三方库,以及现在的新式浏览器内置的Promise,都是符合
Promise\A+规范要求的
参考资料
- MDN-Promise