使用 TDD 的模式,基于 Promise 规范与具体功能点,实现简化版本的 MyPromise
解决回调地狱和异步处理不统一的问题
Promise A+ 规范定义:一个对象或函数,具有符合 Promise A+ 规范的 then 方法,就是一个 Promise
ES6 的 Promise 在满足 Promise A+ 规范的基础上,提供了额外的例如 .catch
、.finnally
、Promise.all
、Promise.resolve
等功能
- Promise 实例化时接受一个函数
it('Promise 实例化时接受一个函数', () => {
assert.throw(() => new MyPromise(1))
assert.throw(() => new MyPromise())
})
- Promise 实例化传递的函数参数立即执行
it('Promise 实例化传递的函数参数立即执行', () => {
const fn = vi.fn()
new MyPromise(fn)
assert.notEqual(fn.mock.lastCall, undefined)
})
- Promise 实例对象在执行时,接收 resolve 和 reject 两个函数作为参数
it('Promise 接收的函数在执行时,内部接收 resolve 和 reject 两个函数作为参数', () => {
const fn = vi.fn()
new MyPromise(fn)
const args = fn.mock.calls[0]
assert.isFunction(args[0])
assert.isFunction(args[1])
})
- resolve 和 rejected 函数接收返回值并修改 Promise 的状态
- 注意 resolve 和 rejected 的 this 指向
it('resolve 和 rejected 函数接收返回值并修改 Promise 的状态', () => {
const p1 = new MyPromise((resolve) => {
resolve(1)
})
assert.equal(p1.state, 'fulfilled')
assert.equal(p1.result, 1)
const p2 = new MyPromise((_, reject) => {
reject("reason")
})
assert.equal(p2.state, 'rejected')
assert.equal(p2.result, "reason")
})
- 函数执行时报错(同步错误),则 Promise 的状态改为 rejected
it('函数执行时报错(同步错误),则 Promise 的状态改为 rejected', () => {
const p = new MyPromise(() => {
throw "error"
})
assert.equal(p.state, 'rejected')
assert.equal(p.result, "error")
})
- Promise 实例的状态一旦确定,不允许再次改变
it('Promise 实例的状态一旦确定,不允许再次改变', () => {
const p = new MyPromise((resolve, reject) => {
resolve(1)
reject('error')
})
assert.equal(p.state, 'fulfilled')
assert.equal(p.result, 1)
})
const FULFILLED = 'fulfilled'
const PENDING = 'pending'
const REJECTED = 'rejected'
export class MyPromise {
state: 'pending' | 'fulfilled' | 'rejected' = PENDING
result = undefined
constructor(fn: Function) {
if (typeof fn !== 'function') {
throw new Error('must be a function')
}
try {
fn(this.resolve.bind(this), this.reject.bind(this))
} catch (err) {
this.reject(err)
}
}
resolve(data: any) {
if (this.state !== PENDING) return
this.result = data
this.state = FULFILLED
}
reject(reason: any) {
if (this.state !== PENDING) return
this.result = reason
this.state = REJECTED
}
}
- Promise 实例对象具有 then 方法,then 方法接受两个函数作为可选参数
it('Promise 实例对象具有 then 方法, then 方法接受两个函数作为可选参数', () => {
const p = new MyPromise(() => { })
assert.isFunction(p.then)
})
- then 方法的返回值是一个 Promise
it('then 方法的返回值是一个 Promise', () => {
const p = new MyPromise(() => { })
assert.instanceOf(p.then(), MyPromise)
})
- then 方法中的回调会在 resolve 或 reject 被调用后(状态变更后)执行
- 当方法为异步执行时,需要将状态处理函数 onFulfilled 与 onRejected 以及当前 then 返回的 Promise 中的 resolve 和 reject 单独维护起来
- 当方法一段时间后获取到了结果,再取出四个处理函数,按状态逻辑调用即可
- 将以上执行 onFulfilled 与 onRejected 的过程抽离到另外一个函数 run 中,在状态发生变更时使用
- 所以 then 方法只负责记录四个状态处理函数,并手动调用一次 run 方法
it('[sync]then 方法中的回调会在 resolve 或 reject 被调用后(状态变更后)执行', () => {
const onFulfilled1 = vi.fn()
const onRejected1 = vi.fn()
const p1 = new MyPromise((resolve) => {
resolve()
})
p1.then(onFulfilled1, onRejected1)
assert.isArray(onFulfilled1.mock.lastCall)
assert.isUndefined(onRejected1.mock.lastCall)
const onFulfilled2 = vi.fn()
const onRejected2 = vi.fn()
const p2 = new MyPromise((_, reject) => {
reject()
})
p2.then(onFulfilled2, onRejected2)
assert.isUndefined(onFulfilled2.mock.lastCall)
assert.isArray(onRejected2.mock.lastCall)
})
it('[async]then 方法中的回调会在 resolve 或 reject 被调用后(状态变更后)执行', async () => {
const onFulfilled1 = vi.fn()
const onRejected1 = vi.fn()
const p1 = new MyPromise((resolve) => {
setTimeout(() => {
resolve(1)
}, 100)
})
p1.then(onFulfilled1, onRejected1);
await new Promise(resolve => setTimeout(resolve, 200))
expect(onFulfilled1).toHaveBeenCalled()
expect(onFulfilled1).toHaveBeenCalledWith(1)
expect(onRejected1).not.toHaveBeenCalled()
})
- 如果 then 的参数不是函数,则忽略
it('如果 then 的参数不是函数,则忽略', () => {
const p = new MyPromise((resolve) => {
resolve(1)
})
// @ts-ignore
p.then(1, 2)
})
- then 可以在同一个 promise 中多次调用,且依据 then 的顺序调用
- 所以负责记录状态处理函数的变量,应该采用数组,每调用一次 then,就将处理函数放入数组中
it('then 可以在同一个 promise 中多次调用,且依据 then 的顺序调用', () => {
const onFulfilled1 = vi.fn()
const onFulfilled2 = vi.fn()
const onFulfilled4 = vi.fn()
const onRejected = vi.fn()
const p = new MyPromise((resolve) => {
resolve(1)
})
p.then(onFulfilled1, onRejected)
p.then(onFulfilled2, onRejected)
// @ts-ignore
p.then(1, onRejected)
p.then(onFulfilled4, onRejected)
expect(onFulfilled1).toHaveBeenCalledWith(1)
expect(onFulfilled2).toHaveBeenCalledWith(1)
expect(onFulfilled4).toHaveBeenCalledWith(1)
expect(onRejected).not.toHaveBeenCalled()
})
const FULFILLED = 'fulfilled'
const PENDING = 'pending'
const REJECTED = 'rejected'
export class MyPromise {
state: 'pending' | 'fulfilled' | 'rejected' = PENDING
result = undefined
callbacks: {
onFulfilled?: Function
onRejected?: Function
resolve?: Function
reject?: Function
}[] = []
constructor(fn: Function) {
if (typeof fn !== 'function') {
throw new Error('must be a function')
}
try {
fn(this.resolve.bind(this), this.reject.bind(this))
} catch (err) {
this.reject(err)
}
}
resolve(data: any) {
if (this.state !== PENDING) return
this.result = data
this.state = FULFILLED
this.run()
}
reject(reason: any) {
if (this.state !== PENDING) return
this.result = reason
this.state = REJECTED
this.run()
}
run() {
if (this.state === PENDING) return
while (this.callbacks.length) {
const { onFulfilled, onRejected, resolve, reject } = this.callbacks.shift()
if (this.state === FULFILLED) {
onFulfilled?.(this.result)
} else {
onRejected?.(this.result)
}
}
}
then(onFulfilled?: Function, onRejected?: Function) {
return new MyPromise((resolve: Function, reject: Function) => {
this.callbacks.push({
onFulfilled: onFulfilled && typeof onFulfilled === 'function' ? onFulfilled : undefined,
onRejected: onRejected && typeof onRejected === 'function' ? onRejected : undefined,
resolve,
reject
})
this.run()
})
}
}
下面开始处理 then 方法的 resolve 和 reject 执行逻辑,以及一些特殊情况
- 当 onFulfilled onRejected 回调不是函数时
- 暗含的意思是对当前 promise 不做处理,可能会留给下一次?
- 所以此时 then 方法返回的新 promise 直接使用当前 promise 的结果
- 即直接调用新 promise 的 resolve 或 reject ,以当前 promise 实例的数据来更新新的 promise 状态
it('then 方法的 onFulfilled onRejected 参数不是函数时,then 方法返回的新 promise 的结果与当前的 promise 保持一致', () => {
const p = new MyPromise((resolve) => {
resolve('p的结果')
})
// expect(p.then(123).result).eql('p的结果')
// expect(p.then(() => { }).result).eq(undefined)
p.then(123).then(result => {
expect(result).eq('p的结果')
})
p.then(() => { }).then(result => {
expect(result).eq(undefined)
})
})
- 当 onFulfilled onRejected 函数执行过程中报错了,则直接 reject
it('当 onFulfilled onRejected 回调是函数时,如果执行报错了,则直接 reject', () => {
const onFulfilled2 = vi.fn()
const onRejected2 = vi.fn()
const p = new MyPromise((resolve) => { resolve(1) })
const errorFulfilled = () => {
throw 123
}
p.then(errorFulfilled).then(onFulfilled2, onRejected2)
expect(onFulfilled2).not.toBeCalled()
expect(onRejected2).toBeCalledWith(123)
})
- 当 onFulfilled onRejected 函数有返回结果,则将新的结果作为下一次链式调用 then 的结果
- 即将新的结果作为本次 then 返回的 promise 的结果
it('当 onFulfilled onRejected 函数有返回结果,则作为下一次链式调用 then 的结果', () => {
const onFulfilled = (result: number) => {
return result + 1
}
const p = new MyPromise((resolve) => { resolve(1) })
return p.then(onFulfilled).then((result) => {
expect(result).eq(2)
})
})
- 当 onFulfilled onRejected 函数的返回结果是一个 Promise,则当前的 then 成功与失败的结果取决于这个返回的 promise
- 将 reject 和 resolve 直接作为这个 promise 的 then 的处理函数
it('当 onFulfilled onRejected 函数的返回结果是一个 Promise,则当前的 then 成功与失败的结果取决于这个返回的 promise', () => {
const onFulfilled = (result: number) => {
return new MyPromise((resolve) => {resolve(result + 1)})
}
const p = new MyPromise((resolve) => { resolve(1) })
return p.then(onFulfilled).then((result) => {
expect(result).eq(2)
})
})
const FULFILLED = 'fulfilled'
const PENDING = 'pending'
const REJECTED = 'rejected'
export class MyPromise {
state: 'pending' | 'fulfilled' | 'rejected' = PENDING
result = undefined
callbacks: {
onFulfilled: Function | undefined
onRejected: Function | undefined
resolve: Function
reject: Function
}[] = []
constructor(fn: Function) {
if (typeof fn !== 'function') {
throw new Error('must be a function')
}
try {
fn(this.resolve.bind(this), this.reject.bind(this))
} catch (err) {
this.reject(err)
}
}
resolve(data: any) {
if (this.state !== PENDING) return
this.result = data
this.state = FULFILLED
this.run()
}
reject(reason: any) {
if (this.state !== PENDING) return
this.result = reason
this.state = REJECTED
this.run()
}
run() {
if (this.state === PENDING) return
while (this.callbacks.length > 0) {
const callback = this.callbacks.shift()
if (!callback) continue
const { onFulfilled, onRejected, resolve, reject } = callback
if (this.state === FULFILLED) {
if (onFulfilled) {
try {
const data = onFulfilled(this.result)
if (isPromiseLike(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
} else {
resolve(this.result)
}
} else {
if (onRejected) {
try {
const data = onRejected(this.result)
if (isPromiseLike(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
} else {
reject(this.result)
}
}
}
}
then(onFulfilled?: any, onRejected?: any) {
return new MyPromise((resolve: Function, reject: Function) => {
this.callbacks.push({
onFulfilled: onFulfilled && typeof onFulfilled === 'function' ? onFulfilled : undefined,
onRejected: onRejected && typeof onRejected === 'function' ? onRejected : undefined,
resolve,
reject
})
this.run()
})
}
}
-
代码逻辑抽离,将 runOne 逻辑放入微队列中执行
-
将状态等变量私有化
const FULFILLED = 'fulfilled'
const PENDING = 'pending'
const REJECTED = 'rejected'
type Callback = {
onFulfilled: Function | undefined
onRejected: Function | undefined
resolve: Function
reject: Function
}
export class MyPromise {
private state: 'pending' | 'fulfilled' | 'rejected' = PENDING
private result = undefined
private callbacks: Callback[] = []
constructor(fn: Function) {
if (typeof fn !== 'function') {
throw new Error('must be a function')
}
try {
fn(this.resolve.bind(this), this.reject.bind(this))
} catch (err) {
this.reject(err)
}
}
resolve(data: any) {
if (this.state !== PENDING) return
this.result = data
this.state = FULFILLED
this.run()
}
reject(reason: any) {
if (this.state !== PENDING) return
this.result = reason
this.state = REJECTED
this.run()
}
private runOne(callback: Callback['onFulfilled'], resolve: Callback['resolve'], reject: Callback['reject']) {
appendToMicroQueue(() => {
if (callback) {
try {
const data = callback(this.result)
if (isPromiseLike(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
} else {
this.state === FULFILLED ? resolve(this.result) : reject(this.result)
}
})
}
private run() {
if (this.state === PENDING) return
while (this.callbacks.length > 0) {
const callback = this.callbacks.shift()
if (!callback) continue
const { onFulfilled, onRejected, resolve, reject } = callback
if (this.state === FULFILLED) {
this.runOne(onFulfilled, resolve, reject)
} else {
this.runOne(onRejected, resolve, reject)
}
}
}
then(onFulfilled?: any, onRejected?: any) {
return new MyPromise((resolve: Function, reject: Function) => {
this.callbacks.push({
onFulfilled: onFulfilled && typeof onFulfilled === 'function' ? onFulfilled : undefined,
onRejected: onRejected && typeof onRejected === 'function' ? onRejected : undefined,
resolve,
reject
})
this.run()
})
}
}
根据 Promise A+ 规范,一行代码搞定
function isPromiseLike(p: any): boolean {
return p && typeof p.then === 'function'
}
区分不同环境与不同浏览器版本
function appendToMicroQueue(fn: Function) {
// @ts-ignore
if (process && typeof process.nextTick === 'function') {
// @ts-ignore
process.nextTick(fn)
} else if (MutationObserver) {
const m = new MutationObserver(fn as MutationCallback)
const node = document.createTextNode('1')
m.observe(node)
node.data = '2'
m.disconnect()
} else {
setTimeout(fn, 0)
}
}
在运行函数放入微队列后,我们在测试中的代码涉及到异步的断言都要使用延时大法重写,以保证在微队列任务执行完毕后再断言