1. 手写Promise
1.0 Promise核心用法-文档链接
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const num = parseInt(Math.random() * 10)
if (num > 5) {
resolve(`成功啦--${num}`)
} else {
reject(`失败啦--${num}`)
}
}, 1000)
})
p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
需求:手写Promise,实现所有功能,并通过Promise/A+测试
- 实现Promise的核心功能:
- Promise:
- 实例方法:
catch,finally - 静态方法:
resolve,reject,race,all,allSettled,any
- 实例方法:
- Promise\A+标准,并跑通872个单元测试
1.1 核心功能
首先明确Promise的核心用法
// 实例化 并管理异步任务
const p = new HMPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
// reject('error')
}, 1000);
})
// then方法获取成功/失败结果
// 参数1:成功时执行的回调函数
// 参数2:失败时执行的回调函数
p.then(res => {
console.log('res:', res)
return 'success2'
}, err => {
console.log('err:', err)
}).then(res2 => {
console.log('res2:', res2)
})
1.1.1 构造函数
核心步骤:
- 定义类
HMPromise - 添加构造函数
constructor - 定义resolve/reject
- 执行回调函数
需求:
- 实现
HMPromise类,可以用如下的方式实例化 - 实例化时传入回调函数
- 回调函数立刻执行
- 回调函数接收函数
resolve和reject
const p = new HMPromise((resolve, reject) => {
resolve('success')
// reject('error')
})
// 1. 定义类
class HMPromise {
// 2. 添加构造函数
constructor(func) {
// 3. 定义resolve/reject
const resolve = (result) => {
console.log('resolve-执行啦:', result)
}
const reject = (result) => {
console.log('reject-执行啦:', result)
}
// 4. 执行回调函数
func(resolve, reject)
}
}
面试回答:
手写Promise-构造函数
- 定义类
HMPromise,内部添加构造函数constructor,构造函数需要接收回调函数func - 在构造函数中定义
resolve和reject - 构造函数内部调用
func并将resolve和reject传入:func(resolve,reject)
1.1.2 状态、成功or失败原因
需求:
HMPromise增加state属性,只能是如下3个值pending:待定,默认状态fulfilled:已兑现,操作成功rejected:已拒绝,操作失败
HMPromise增加result属性,记录成功/失败原因- 调用
resolve或reject,修改状态,并记录成功/失败原因
const p = new HMPromise((resolve, reject) => {
resolve('success') // pending -> fulfilled
// reject('error') // pending -> rejected
})
p.state // 状态
p.result // 原因
核心步骤:
- 添加状态定义常量保存状态,避免硬编码(pending/fulfilled/rejected)
HMPromise中定义- 属性:
state保存状态,result成功/失败原因 - 修改
state的私有方法,修改状态并记录result - 注意:
state只有在pending时,才可以修改,且不可逆
- 属性:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 1. 添加状态
state = PENDING
// 2. 添加原因
result = undefined
constructor(func) {
// 3. 调整resolve/reject
// 4. 状态不可逆
// 改状态: pending->fulfilled
// 记录原因
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
}
}
// 改状态: pending->rejected
// 记录原因
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则更改状态并记录拒绝原因
1.1.3 then方法
1.1.3.1 成功和失败回调
需求:
- then方法的回调函数1: 状态变为
fulfilled时触发,并获取成功结果 - then方法的回调函数2: 状态变为
rejected时触发,并获取失败原因 - then方法的回调函数1或2没有传递的特殊情况处理,参考:then方法的参数
const p = new HMPromise((resolve, reject) => {
resolve('success')
// reject('error')
})
p.then(res => {
console.log('成功回调:', res)
}, err => {
console.log('失败回调:', err)
})
核心步骤:
-
添加实例方法:增加
then方法,根据不同的状态执行对应的回调函数,并传入result- 参数1:成功的回调函数
- 参数2:失败的回调函数
-
参数判断(参考文档)
没有传递
onFulfilled,onRejected时设置默认值-
执行成功的回调
-
执行失败的回调
-
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 状态
state = PENDING
// 原因
result = undefined
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
}
}
func(resolve, reject)
}
// 1. 添加实例方法
then(onFulfilled, onRejected) {
// 2. 参数判断(参考文档)
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
// 2.1 执行成功回调
// 2.2 执行失败回调
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并传入兑现或拒绝原因
1.1.3.2 异步和多次调用
需求:
- 实例化传入的回调函数,内部支持异步操作
then方法支持多次调用(非链式编程)
const p = new HMPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
// reject('error')
}, 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内部
- 调用成功回调:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 状态
state = PENDING
// 原因
result = undefined
// 1. 定义实例属性
#handlers = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
// 3. 调用成功回调
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
// 4. 调用失败回调
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
func(resolve, reject)
}
// then方法
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) {
// 2. 保存回调函数
this.#handlers.push({
onFulfilled, onRejected
})
}
}
}
面试回答
手写Promise-then方法-异步和多次调用
-
HMPromise添加私有属性#handlers- 保存then方法调用时状态为
pending的回调函数 - 格式为对象数组:
[{onFulfilled,onRejected}...]
- 保存then方法调用时状态为
-
构造函数内部调整
resolve和reject的逻辑:- 调用
resolve时取出数组#handlers中的所有onFulfilled回调函数进行调用 - 调用
reject时取出数组#handlers中的所有onRejected回调函数进行调用
- 调用
1.1.4 异步任务
1.1.4.1 核心api
需求:
- 如下代码打印结果为
1,2,4,3 - 核心:让then方法的回调函数以异步任务的方式执行
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 queueMicrotask -------------
// node v11 ie 不支持
// console.log('top')
queueMicrotask(() => {
// ....
})
// console.log('bottom')
// ------------- 异步任务2 MutationObserver -------------
// node 不支持 ie11
console.log('top')
// 创建并返回一个新的观察器,它会在触发指定 DOM 事件时,调用指定的回调函数
const obs = new MutationObserver(() => {
console.log('MutationObserver-run')
})
// 创建div
const divNode = document.createElement('div')
// 监听创建的div ,监听子节点改变
obs.observe(divNode, { childList: true })
// 修改内容触发回调函数
divNode.innerText = 'itheima 666'
console.log('bottom')
// ------------- 异步任务3 setTimeout -------------
// 这个都熟悉,就不测试了啦
面试回答
请问可以使用哪些方式开启异步任务:
Promise.then: 我们是手写Promise,故不选用这个queueMicrotask:新式浏览器均支持,node11开始支持,ie不支持MutationObserver:新式浏览器均支持,ie11开始支持setImmediate: 新式浏览器只有edge支持,ie10开始支持setTimeout:浏览器支持,node支持
1.1.4.2 函数封装
需求:
- 封装函数
runMicrotask内部执行异步任务 - 使用
runMicrotask让then方法的回调函数为异步任务
核心步骤:
- 封装函数
runMicrotask并传入回调函数 - 内部依次判断:
queueMicrotask、MutationObserver、setTimeout并使用即可 - 使用
runMicrotask开启执行异步任务即可
// 1. 定义函数
function runAsynctask(callback) {
// 2. 调用核心api(queueMicrotask,MutationObserver,setTimeout)
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 = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
func(resolve, reject)
}
// then方法
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
// 3. 使用封装函数
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种状态时的回调函数,使用封装的函数包装一次
1.1.5 链式编程
1.1.5.1 fulfilled状态
核心步骤:
- 返回新 Promise 实例
- 获取返回值
- 处理返回值
- 处理异常
- 处理返回 Promise
- 处理重复引用
1.1.5.1.1 返回值+异常
需求:
then的链式编程- 目前只考虑
then的第一个回调函数- 返回普通值
- 内部出现异常
const p = new HMPromise((resolve, reject) => {
resolve(1)
})
p.then(res => {
console.log(res)
// throw 'throw-error'
return 2
}).then(res => {
console.log(res)
}, err => {
console.log(err)
})
核心步骤:
- 调整
then方法,返回一个新的HMPromise对象 - 使用
try-catch捕获异常,并通过reject传递 - 内部获取
onFulfilled的执行结果,并通过resolve传递
// 执行异步任务
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)
}
}
/**
* 链式编程-处理异常和普通内容(fulfilled状态)
* 1. 返回新Promise实例
* 2. 获取返回值
* 2.1 处理返回值
* 2.2 处理异常
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 状态
state = PENDING
// 原因
result = undefined
// 回调函数数组
#handlers = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
func(resolve, reject)
}
// then方法
// 1. 返回新Promise实例
// 2. 获取返回值
// 2.1 处理返回值
// 2.2 处理异常
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
// 1. 返回新Promise实例
const p2 = new HMPromise((resolve, reject) => {
if (this.state === FULFILLED) {
runAsynctask(() => {
// 2. 获取返回值
try {
const x = onFulfilled(this.result)
// console.log('x:', x)
// 2.1 处理返回值
resolve(x)
} catch (error) {
// 2.2 处理异常
// console.log('捕获异常:', 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传递
1.1.5.1.2 处理返回Promise
需求:
then的链式编程- 目前只考虑
then的第一个回调函数- 返回
Promise
- 返回
const p = new HMPromise((resolve, reject) => {
resolve(1)
})
p.then(res => {
return new HMPromise((resolve, reject) => {
resolve(2)
// reject('error')
})
}).then(res => {
console.log('p2:', res) // 2
}, err => {
console.log('p2:', err) // error
})
核心步骤:
- 判断是否为
HMPromise实例 - 调用
then方法依次传入回调函数
// 执行异步任务
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)
}
}
/**
* 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 状态
state = PENDING
// 原因
result = undefined
// 回调函数数组
#handlers = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
func(resolve, reject)
}
// then方法
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)
// 1.处理返回Promise
if (x instanceof HMPromise) {
// console.log('HMPromise实例')
// 2. 调用then方法
// x.then(res => console.log(res), err => console.log(err))
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传递即可
1.1.5.1.3 处理重复引用
需求:
then中返回的then方法返回的Promise实例报错- 注:下列代码中的
p2

const p = new HMPromise((resolve, reject) => {
resolve(1)
})
const p2 = p.then(res => {
return p2
})
p2.then(
res => { },
err => console.log('err:', err))
核心步骤:
- 判断是否相同,抛出异常
// 执行异步任务
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)
}
}
/**
* 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 状态
state = PENDING
// 原因
result = undefined
// 回调函数数组
#handlers = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
func(resolve, reject)
}
// then方法
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)
// 1. 处理重复引用
if (x === p2) {
// console.log('返回了p2')
// 2. 抛出错误 Chaining cycle detected for promise #<Promise>
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>')
1.1.5.2 rejected状态
需求:
then的第二个回调函数,执行reject时的链式编程
const p = new HMPromise((resolve, reject) => {
reject(1)
})
const p2 = p.then(undefined, err => {
throw 'error'
// return p2
// return 2
// return new HMPromise((resolve, reject) => {
// resolve('HMPromise-2')
// })
})
p2.then(res => {
console.log('p2-res:', res)
}, err => {
console.log('p2-err:', err)
})
核心步骤:
- 处理异常:
onRejected的异常 - 获取返回值:
onRejected的返回值 - 将
fulfilled状态中的处理逻辑抽取为函数resolvePromise并复用 fulfilled和rejected状态中调用函数resolvePromise
// 执行异步任务
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)
}
}
/**
* 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 状态
state = PENDING
// 原因
result = undefined
// 回调函数数组
#handlers = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
func(resolve, reject)
}
// then方法
// 1. 处理异常
// 2. 获取返回值
// 3. 抽取函数
// 4. 调用函数
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)
// 4. 调用函数
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)
// }
} catch (error) {
reject(error)
}
})
}
else if (this.state === REJECTED) {
runAsynctask(() => {
// 1. 处理异常
try {
// 2. 获取返回值
const x = onRejected(this.result)
// console.log('x:', x)
// 4. 调用函数
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
}
}
// 3. 抽取函数
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状态时调用函数复用逻辑
1.1.5.3 pending状态
需求:
- 执行异步操作时,支持链式编程
const p = new HMPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
const p2 = p.then(res => {
throw 'error'
// return p2
// return 2
// return new HMPromise((resolve, reject) => {
// resolve('resolve-2')
// // reject('reject-2')
// })
})
p2.then(res => {
console.log('p2-res:', res)
}, err => {
console.log('p2-err:', err)
})
核心步骤:
-
处理异常:
fulfilled状态时推入回调函数数组时增加try-catch
-
获取返回值:
- 推入数组时,增加获取返回值的操作
-
调用上一节封装的函数
resolvePromise
// 执行异步任务
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)
}
}
/**
* 链式编程-处理Promise(fulfilled状态)
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class HMPromise {
// 状态
state = PENDING
// 原因
result = undefined
// 回调函数数组
#handlers = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
func(resolve, reject)
}
// then方法
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(() => {
// 1. 处理异常
try {
// 2.获取返回值
const x = onFulfilled(this.result)
// 3.调用函数
resolvePromise(p2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}, onRejected: () => {
runAsynctask(() => {
// 1. 处理异常
try {
// 2.获取返回值
const x = onRejected(this.result)
// 3.调用函数
resolvePromise(p2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
})
}
})
return p2
}
}
面试回答:
手写Promise-链式编程-pending状态
then方法中pending状态时推入数组的函数增加try-catch捕获异常- 获取推入数组的回调函数的返回值
- 调用上一节封装的函数并传入获取的值
小结:
到目前已经将核心功能全部实现啦,接下来开始实现后续功能
- 实现Promise的核心功能:
- Promise:
- 实例方法:
-
catch -
finally
-
- 静态方法:
-
resolve -
reject -
race -
all -
allSettled -
any
-
- 实例方法:
- Promise\A+标准,并跑通872个单元测试
1.2 实例方法
1.2.1 catch
需求:
- 实现实例方法
catch,可以实现如下调用
const p = new HMPromise((resolve, reject) => {
reject('reject-error')
// throw 'throw-error'
})
p.then(res => {
console.log('res:', res)
}).catch(err => {
console.log('err:', err)
})
核心步骤:
- 参考文档,catch等同于:
then(undefined,onRjected) - 直接添加
catch方法,内部调用then - 使用
try-catch包裹constructor中的func捕获异常
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 = [] // [{onFulfilled,onRejected}...]
// 构造函数
constructor(func) {
// pending->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#handlers.forEach(({ onRejected }) => {
onRejected(this.result)
})
}
}
// 2. 处理异常
try {
func(resolve, reject)
} catch (error) {
// console.log('error:', error)
reject(error)
}
}
// then方法
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方法
* 1. 内部调用then方法
* 2. 处理异常
* */
catch(onRejected) {
// 1. 内部调用then方法
return this.then(undefined, onRejected)
}
}
面试回答
手写Promise-实例方法catch
-
定义
catch方法,接收拒绝的回调函数onRejected -
catch方法的本质是内部调用then方法 -
调用形式为第一个回调函数传入
undefined,第二个回调函数传入onRejected即可
1.2.2 finally
需求:
- 无论成功失败都会执行
finally的回调函数 - 回调函数不接受任何参数
const p = new HMPromise((resolve, reject) => {
// resolve('resolve-res')
// reject('reject-error')
// throw 'throw-error'
})
p.then(res => {
console.log('res:', res)
}).catch(err => {
console.log('err:', err)
}).finally(() => {
console.log('finally')
})
核心步骤:
- 参考文档:finally方法类似于调用
then(onFinally,onFinally),且不接受任何回调函数 - 注意:
- 版面问题,这里只保留函数部分的逻辑,其他代码未改动
- 后续未特殊说明,只保留函数部分代码
finally(onFinally) {
return this.then(onFinally,onFinally)
}
面试回答:
手写Promise-实例方法finally
- 添加
finally方法,接收最终执行的回调函数onFinally finally方法的本质为内部调用then方法- 调用形式为第一个和第二个回调函数均传入
onFinally即可
finally(onFinally) {
return this.then(onFinally,onFinally)
}
到目前已经将实例方法都实现啦
- 实现Promise的核心功能:
- Promise:
- 实例方法:
-
catch -
finally
-
- 静态方法:
-
resolve -
reject -
race -
all -
allSettled -
any
-
- 实例方法:
- Promise\A+标准,并跑通872个单元测试
1.3 静态方法
1.3.1 静态方法resolve
需求:
- 返回一个带有成功原因的
Promise对象
HMPromise.resolve(new HMPromise((resolve, reject) => {
// resolve('resolve')
// reject('reject')
// throw 'error'
})).then(res => {
console.log('res:', res)
}, err => {
console.log('err:', err)
})
HMPromise.resolve('itheima').then(res => {
console.log(res)
})
核心步骤:
- 增加静态方法
resolve,根据传入的值返回不同的结果即可
static resolve(value) {
// 1. 判断传入值
if (value instanceof HMPromise) {
// 2.1 Promise直接返回
return value
}
// 2.2 转为Promise并返回(fulfilled状态)
// return new HMPromise((resolve, reject) => {
return new HMPromise((resolve) => {
resolve(value)
})
}
面试回答:
手写Promise-静态方法resolve
-
通过
static关键字添加静态方法resolve,接收参数value -
内部判断传入的值
-
如果是
Promise实例,直接返回 -
其他的值,创建
Promise实例并返回,内部通过resolve(value)传递value
-
1.3.2 静态方法reject
需求:
- 返回一个带有拒绝原因的
Promise对象
HMPromise.reject('error').catch(res => {
console.log(res)
})
核心步骤:
- 添加静态方法
- 返回
rejected状态的Promise
static reject(value) {
// 1. 返回rejected状态的Promise
// new HMPromise((resolve,reject)=>{
return new HMPromise((undefined, reject) => {
reject(value)
})
}
面试回答
手写Promise-静态方法reject
- 添加静态方法
reject并接收参数value - 内部返回一个拒绝状态的
Promise实例即可
1.3.3 静态方法race
需求:
- 接收Promise数组,数组中第一个Promise敲定时,获取
成功/失败结果 - 传入的参数不是数组,直接报错

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 - 判断是否未数组,不是直接报错
- 等待第一个敲定
static race(promises) {
// 1. 返回Promise
return new HMPromise((resolve, reject) => {
// 2. 判断是否为数组
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 3. 等待第一个敲定
promises.forEach(p => {
// p.then
HMPromise.resolve(p).then(res => { resolve(res) }, err => { reject(err) })
})
})
}
面试回答:
手写Promise-静态方法race
- 添加静态方法
race接收参数promises - 内部返回一个新的
Promise实例,在返回的Promise实例中:- 判断参数是否为数组,不是通过
reject传递错误 - 遍历Promise数组,通过
resolve静态方法等待每一个兑现 - 任何一个兑现,调用
resolve传递兑现结果 - 任何一个拒绝,调用
reject传递拒绝原因
- 判断参数是否为数组,不是通过
1.3.4 静态方法all
需求:
- 接收Promise数组
- 所有Promise都成功时,返回一个成功的Promise对象及成功数组
- 任何一个Promise失败,返回一个失败的Promise对象及第一个失败原因
const p1 = HMPromise.resolve(1)
const p2 = new HMPromise((resolve, reject) => {
setTimeout(() => {
resolve(2)
// reject('error')
}, 1000)
})
const p3 = 3
HMPromise.all([p1, p2, p3]).then(res => {
console.log('res:', res)
}, err => {
console.log('err:', err)
})
核心步骤:
-
返回Promise
-
判断参数是否未数组:
- 不是:直接报错
-
是:
-
空数组直接兑现
-
处理全部兑现:记录结果->判断全部兑现 3. 处理第一个拒绝
-
static all(promises) {
// 1. 返回Promise实例
return new HMPromise((resolve, reject) => {
// 2. 判断是否为数组
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 3. 空数组直接兑现
promises.length === 0 && resolve(promises)
// 4.1 记录结果
const results = []
let count = 0
promises.forEach((p, index) => {
HMPromise.resolve(p).then(res => {
// results.push 无法保证 结果的顺序和Promise数组的顺序一致
// index 和 Promise实例的索引一致,保证顺序
results[index] = res
// 4.2 判断全部兑现
count++
count === promises.length && resolve(results)
}, err => {
// 5. 处理第一个拒绝
reject(err)
})
})
})
}
面试回答:
手写Promise-静态方法all
- 添加静态方法
all - 内部返回
Promise实例,在返回的Promise实例中:- 判断参数是否为数组,不是通过
reject传递错误 - 空数组直接以空数组为结果进行兑现
- 遍历
Promise数组,通过resolve静态方法等待结果- 处理全部兑现:
- 通过数组记录结果,用索引的方式来添加,目的是保证结果的顺序和
Promise数组的顺序一致 - 通过兑现次数进行判断,因为是通过索引的方式记录结果,如果第一次兑现的是最后一个,那么数组的长度就已经和Promise数组的长度一致了,所以需要通过兑现次数来进行判断
- 通过数组记录结果,用索引的方式来添加,目的是保证结果的顺序和
- 任意一个拒绝,调用
reject传递拒绝原因
- 处理全部兑现:
- 判断参数是否为数组,不是通过
1.3.5 静态方法allSettled
需求:-传送门
- 传入
Promise数组,当所有对象都已敲定时 - 返回一个新的
Promise对象及以数组形式保存的结果
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'}
- 处理兑现:
static allSettled(promises) {
// 1. 返回Promise
return new HMPromise((resolve, reject) => {
// 2. 数组判断
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 3. 为空直接敲定
promises.length === 0 && resolve(promises)
// 4. 等待全部敲定
// 4.1 记录结果
const results = []
let count = 0
promises.forEach((p, index) => {
HMPromise.resolve(p).then(res => {
// 4.2 处理兑现{status:'fulfilled',value:''}
results[index] = { status: FULFILLED, value: res }
count++
count === promises.length && resolve(results)
}, err => {
// 4.3 处理拒绝{status:'rejected',reason:''}
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传递结果数组
1.3.6 静态方法any
需求:-传送门
- 传入
Promise数组,- 任何一个
Promise对象敲定时,返回一个新的Promise对象,及对应的结果 - 所有Promise都被拒绝时,返回一个包含所有拒绝原因的
AggregateError错误数组
- 任何一个
const p1 = new HMPromise((resolve, reject) => {
setTimeout(() => {
reject(1)
}, 2000)
})
const p2 = 2
const p3 = new HMPromise((resolve, reject) => {
setTimeout(() => {
resolve(3)
// reject(3)
}, 1000)
})
HMPromise.any([p1, p2, p3]).then(res => {
console.log('res:', res)
}, err => {
console.dir(err)
})
核心步骤:
- 返回Promise
- 判断是否为数组
- 不是:报错
- 是:
- 空数组:直接拒绝
- 等待结果:
- 第一个兑现
- 全部拒绝
static any(promises) {
// 1. 返回Promise,数组判断
return new HMPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 2. 空数组直接拒绝
promises.length === 0 && reject(new AggregateError(promises, 'All promises were rejected'))
// 3. 等待结果
const errors = []
let count = 0
promises.forEach((p, index) => {
HMPromise.resolve(p).then(res => {
// 3.1 第一个兑现
resolve(res)
}, err => {
// 3.2 全部拒绝
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:
- 实例方法:
-
catch -
finally
-
- 静态方法:
-
resolve -
reject -
race -
all -
allSettled -
any
-
- 实例方法:
-
Promise\A+标准,并跑通872个单元测试
1.4 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. 提供deferred方法,返回对象{promise,resolve,reject} 1.1 promise: pending状态的promise实例(自己手写的Promise) 1.2 resolve: 以传入的原因兑现promise 1.3 reject: 以传入的原因拒绝promise// 将我们自己手写的Promise拷贝到一个单独的文件,并在底部加上 module.exports = { deferred() { const res = {} // 自己手写的Promise res.promise = new HMPromise((resolve, reject) => { // 内部将resolve和reject赋值上去 res.resolve = resolve res.reject = reject }) return res } } -
下包:
-
初始化项目:
npm init -y -
下包:
npm i promises-aplus-tests -D
-
-
配置并执行命令:
-
package.json的scripts中加入 -
注:
HMPromise是文件名,根据实际情况调整自己的文件名即可"test": "promises-aplus-tests HMPromise" -
执行命令:
npm run test
-
测试:
我们目前的写法中,没有考虑所有的边界情况,测试时会在2.3.3开始出错
只需要将resolvePromise函数替换为如下写法即可:
- 函数名,参数顺序和原函数一致
- 函数内部使用,序号+说明的方式对Promise\A+的标准进行标注
大伙可以参考注释对比确认还需要考虑哪些便捷情况,
// 符合Promise\A规范(考虑了各种边界情况)
function resolvePromise(p2, x, resolve, reject) {
// 2.3.3.1 如果p2和x引用同一个对象,通过TypeError作为原因来拒绝pormise
if (x === p2) {
throw new TypeError('Chaining cycle detected for promise');
}
/**
* 2.3.3.2 如果x是一个promise,采用他的状态
* 2.3.3.3.1 如果x是pengding状态,promise必须保持等待状态,直到x被fulfilled或rejected
* 2.3.3.3.2 如果x是fulfilled状态,用相同的原因解决promise
* 2.3.3.3.3 如果x是rejected状态,用相同的原因拒绝promise
* */
if (x instanceof HMPromise) {
x.then(y => {
resolvePromise(p2, y, resolve, reject)
}, reject);
}
// 2.3.3 如果x是一个对象或者函数
else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
// 2.3.3.1 让then成为x.then
try {
var then = x.then;
} catch (e) {
// 2.3.3.2 如果检索属性x.then抛出了异常e,用e作为原因拒绝promise
return reject(e);
}
/**
* 2.3.3.3 如果then是一个函数,通过call调用他,并且将x作为他的this(参数1)
* 调用then时传入2个回调函数:
* 第一个参数叫做resolvePromise(对应到的参数2)
* 第二个参数叫做rejectPromise(对应到参数3)
* */
if (typeof then === 'function') {
// 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者同一参数被调用了多次,只采用第一次调用,后续的调用会被忽略(观察called后续的赋值+判断)
let called = false;
try {
then.call(
x,
// 2.3.3.3.1 如果 resolvePromise 以 成功原因 y 为参数被调用,继续执行 resolvePromise
y => {
if (called) return;
called = true;
resolvePromise(p2, y, resolve, reject);
},
// 2.3.3.3.2 如果 rejectPromise 以拒绝原因 r 为参数被调用,用 r 拒绝 promise
r => {
if (called) return;
called = true;
reject(r);
}
)
}
// 2.3.3.3.4 如果调用then抛出异常
catch (e) {
// 2.3.3.3.4.1 如果resolvePromise或rejectPromise已经被调用,忽略它
if (called) return;
called = true;
// 2.3.3.3.4.2 否则以 e 作为拒绝原因 拒绝promise
reject(e);
}
} else {
// 2.3.3.4 如果then不是函数,用 x 作为原因 兑现promise
resolve(x);
}
} else {
// 2.3.4 如果then不是对象或函数,用 x 作为原因 兑现promise
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+规范要求的
1.5 参考资料
2. 函数柯里化
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化 作为一种高阶技术,可以提升函数的复用性和灵活性。
2.1 什么是函数柯里化
函数柯里化 (Currying) 是一种将多个参数的函数转换为单个参数函数的技术
转换完毕之后的函数:只传递函数的一部分参数来调用,让他返回一个新的函数去处理剩下的参数。
例子:
// 调整函数 sum
function sum(num1, num2) {
return num1 + num2
}
// 改写为 可以实现如下效果
console.log(sum(1)(2))//
核心步骤:
sum改为接收一个参数,返回一个新函数- 新函数内部将参数1,参数2累加并返回
function sum(num1) {
return function (num2) {
return num1 + num2
}
}
面试回答:
什么是函数柯里化
- 函数柯里化是一种将多个参数的函数转换为单个参数函数的技术
- 转换完毕之后的函数只需要传递一部分参数进行调用,并且会返回一个新的函数去处理剩下的参数
2.2 柯里化面试题
2.2.1 全局变量
柯里化在面试的时候一般以笔试题出现,比如
需求:
function sum(a, b, c, d, e) {
return a + b + c + d + e
}
// 改写函数sum实现:参数传递到5个即可实现累加
// sum(1)(2)(3)(4)(5)
// sum(1)(2,3)(4)(5)
// sum(1)(2,3,4)(5)
// sum(1)(2,3)(4,5)
核心步骤:
- 接收不定长参数
- 存储已传递的参数
- 判断长度
- 满足5:累加
- 不满足:继续返回函数本身
let nums = []
function currySum(...args) {
nums.push(...args)
if (nums.length >= 5) {
return nums.reduce((prev, curv) => prev + curv, 0)
} else {
return currySum
}
}
面试回答:
柯里化面试题-全局变量
- 定义数组保存参数
- 函数接收不定长参数
- 调用时将传入的参数,添加到数组中,并判断数组长度:
- 满足长度要求:累加并返回结果
- 未达到长度要求:继续返回函数本身
2.2.2 使用闭包
需求:
- 使用闭包将上一节代码中的全局变量,保护起来
- 支持自定义累加的参数个数
function sumMaker(length){
// 逻辑略
}
// 支持5个累加
const sum5 = sumMaker(5)
// 支持7个累加
const sum7 = sumMaker(7)
sum7(1,2,3)(4,5,6,7)
核心步骤:
- 定义外层函数:
- 定义参数
length - 将全局变量迁移到函数内
- 定义参数
- 定义内层函数:
- 参数长度判断,使用传入的参数
length - 直接复用上一节的逻辑,并返回
- 参数长度判断,使用传入的参数
function sumMaker(length) {
let nums = []
function inner(...args) {
nums.push(...args)
if (nums.length >= length) {
return nums.reduce((prev, curv) => prev + curv, 0)
} else {
return inner
}
}
return inner
}
面试回答:
柯里化面试题-使用闭包
- 定义函数。接收参数,用来确定参数个数
- 内部将上一节的逻辑拷贝进去
- 返回原函数
- 通过这样的调整,可以让我们自定义参数的个数,并且没有上一节的全局变量数组
2.3 柯里化实际应用
2.3.1 类型判断
通过参数复用,实现一个类型判断生成器函数
需求:
- 将下列4个类型判断函数,改写为通过函数
typeOfTest动态生成
// 有如下4个函数
function isUndefined(thing) {
return typeof thing === 'undefined'
}
function isNumber(thing) {
return typeof thing === 'number'
}
function isString(thing) {
return typeof thing === 'string'
}
function isFunction(thing) {
return typeof thing === 'function'
}
// 改为通过 typeOfTest 生成:
const typeOfTest =function(){
// 参数 和 逻辑略
}
const isUndefined = typeOfTest('undefined')
const isNumber = typeOfTest('number')
const isString = typeOfTest('string')
const isFunction = typeOfTest('function')
// 可以通过 isUndefined,isNumber,isString,isFunction 来判断类型:
isUndefined(undefined) // true
isNumber('123') // false
isString('memeda') // true
isFunction(() => { }) // true
核心步骤:
typeOfTest接收参数type用来接收判断的类型- 内部返回新函数,接收需要判断的值,并基于
type进行判断 - 使用箭头函数改写为最简形式~~传送门-github
const typeOfTest = (type) => {
return (thing) => {
return typeof thing === type
}
}
// 简化为:
const typeOfTest = type => thing => typeof thing === typ
面试回答:
柯里化实际应用-类型判断
-
定义函数,接收需要判断的类型名
-
内部返回一个新的函数,
-
新函数接收需要判断的具体的值
-
新函数内部根据外层函数传入的类型,以及传入的值进行判断并返回结果
-
2.3.2 固定参数
依旧是一个参数复用的实际应用
需求:
- 将如下3个请求的函数(都是post请求),变为通过
axiosPost函数动态生成 - 实现函数
axiosPost
// 项目开发中不少请求的 请求方法 是相同的,比如
axios({
url: 'url',
method: 'get'
})
axios({
url: 'url',
method: 'get',
params: {
//
}
})
axios({
url: 'url',
method: 'post',
data: ''
})
axios({
url: 'url',
method: 'post',
data: '',
headers: {
}
})
// 固定请求参数,请求方法固定,其他参数从外部传递进来
// 需求: 实现方法requestWithMethod 支持如下调用
requestWithMethod('get')({
url: '',
params: {},
headers: {}
})
requestWithMethod('post')({
url: '',
headers: {},
data: {}
})
核心步骤:
- 函数内部固定请求方法:post
- 函数内部调用
axios发请求即可 axios内部就是这样实现的
function requestWithMethod(method) {
return (config) => {
return axios({
method,
...config
})
}
}
面试回答:
柯里化实际应用-固定参数
-
函数柯里化是一种函数式编程思想:将多个参数的函数转换为单个参数函数,调用时返回新的函数接收剩余参数
-
常见面试题,将函数改写为如下调用新式:核心思想就是返回新的函数,根据已经记录的参数长度判断:
-
长度符合要求:累加
-
长度不符合要求:继续返回
function sum(a, b, c, d, e) { return a + b + c + d + e } // 改写函数sum实现:参数传递到5个即可实现累加 // sum(1)(2)(3)(4)(5) // sum(1)(2,3)(4)(5) // sum(1)(2,3,4)(5) // sum(1)(2,3)(4,5) -
-
常见应用:固定参数,比如
axios中的:- 类型判断函数
- get,post,put等别名方法
- 就用到了柯里化的思想