从Promise本质开始(三):TDD方式实现基于Promises/A+规范的复杂Promise

前情提要:之前为手动实现 Promise 搭建了环境,然后还基于 Promises/A+ 规范实现了一个简单的 Promise。这次本文就接着上面的补充,实现一个复杂的 Promise。

项目链接

2.2.7

2.2.7 then must return a promise

2.2.7 promise2 = promise1.then(onFulfilled, onRejected);

这里就是 Promise.then 必须返回一个 Promise

测试用例

it('2.2.7 then必须返回一个promise', () => {
const promise = new Promise(resolve => {
resolve()
})
const promise2 = promise.then(() => {})
// 断言 promise2 是一个 Promise
assert(promise2 instanceof Promise)
})

源码

class PromiseComplex {
...
then(succeed?, fail?) {
...
// 将函数 push 进 callbacks 中
this.callbacks.push(handle)
return new PromiseComplex(() => {})
}
}

export default PromiseComplex

这里需要做的操作就是 then 函数中需要返回一个 Promise 即可,注意这里的 Promise 是自己的写的 PromiseComplex

2.2.7.1

2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x)

Promises/A+ 规范的 2.2.7.1 里面这句话什么意思?就是说,如果 onFulfilled 或者 onRejected 函数返回了一个 x 的值, 那么就运行 Promise 的 Resolution 步骤,即运行 [[Resolve]](promise2, x)

云里雾里的,其实这句话的意思就是,在 promise 内部执行 resolve 或者 reject 函数时,会调用一个成功或者失败的函数,这个函数会返回一个值叫 x,然后再把这个值 x 跟一个新的 promise 传给一个叫做 Resolve 的函数。这个叫做 Resolve 的函数干了什么事情呢,它会将这个值 x 变成 then 里面传的回调的参数。这么说可能不太直观,接着看下面的测试用例应该会明白一些。

测试用例

it(`2.2.7.1 如果 then(success, fail) 中的 success 返回一个值 x,
运行 Promise Resolution Procedure [[Resolve]](promise2, x)`, done => {
const promise = new Promise(resolve => {
resolve()
})
// 注意这里的返回值 x 为 '123',在 then 的回调传的参数 result 中可以获取到
promise.then(() => '123').then((result) => {
assert.equal(result, '123')
done()
})
})

it(`2.2.7.1.2 x 是一个 promise 实例`, done => {
const promise = new Promise(resolve => {
resolve()
})
const fn = sinon.fake()
promise
.then(() => new Promise((resolve) => resolve()))
.then(fn)
setTimeout(() => {
assert.isTrue(fn.called)
done()
}, 10) // 这里有个小 bug, 如果将延时时间改为 0 则不通过
})

it(`2.2.7.1.2 x 是一个 promise 实例, 测试第一个 then 的失败回调`, done => {
const promise = new Promise(resolve => {
resolve()
})
const fn = sinon.fake()
promise
.then(() => new Promise((resolve, reject) => reject()))
.then(null, fn)
setTimeout(() => {
assert.isTrue(fn.called)
done()
}, 0)
})

it(`2.2.7.1.2 fail 的返回值是一个 promise 实例`, done => {
const promise = new Promise((resolve, reject) => {
reject()
})
const fn = sinon.fake()
promise
.then(null, () => new Promise((resolve, reject) => resolve()))
.then(fn)
setTimeout(() => {
// 这里主要看 promise.then 里的 Promise
// 如果是成功则调用 succeed 函数
// 如果是失败则调用 fail 函数
assert(fn.called)
done()
}, 0)
})

it(`2.2.7.1.2 fail 的返回值是一个 promise 实例, 且失败了`, done => {
const promise = new Promise((resolve, reject) => {
reject()
})
const fn = sinon.fake()
promise
.then(null, () => new Promise((resolve, reject) => reject()))
.then(null, fn)
setTimeout(() => {
assert(fn.called)
done()
}, 0)
})

源码

那么测试用例有了,源码应该怎么考虑呢?答案很简单,跟着规范来写。首先我们这里因为是用类写的,所以我们可以使用 Promise.Resovle(x) 的方式来表达 2.2.7.1 规范中出现的调用方式。这里可以将 Resolve 这个函数的名字改动下,我们叫它 resolveWith 吧,然后在源码中定义 resolveWith

class PromiseComplex {
...
resolveWith(x) {
//TODO
}
}

因为定义调用 resolveWith 的方式是通过 Promise.resolveWith 的方式调用的,而这个 Promise 根据规范是一个 promise2,也就意味着这是一个新的 Promise,还记得之前源码中的 then 中的 return new PromiseComplex(() => {}) 么,现在我们这么做

class PromiseComplex {
...
then(succeed?, fail?) {
const handle = []
if (typeof succeed === 'function') {
handle[0] = succeed
}
if (typeof fail === 'function') {
handle[1] = fail
}
// 将下一个 promise 放入 handle[2] 中
handle[2] = new PromiseComplex(() => {})
// 将函数 push 进 callbacks 中
this.callbacks.push(handle)
return handle[2]
}
resolveWith(x) {
//TODO
}
}

这里将这个新的 Promise 存在 handle 这个数组的第三个元素中,然后再返回就好。接下来就需要对 resolve 以及 reject 中改写下逻辑了

class PromiseComplex {
resolve(result) {
...
// 遍历 callbacks, 调用所有的 handle
this.callbacks.forEach(handle => {
const succeed = handle[0]
// 注意这里拿到了规范中定义的 promise2
const nextPromise = handle[2]
if (typeof succeed === 'function') {
const x = succeed.call(undefined, result)
// 2.2.7.1 如果onFulfilled或onRejected返回一个值x,
// 运行 Promise Resolution Procedure [[Resolve]](promise2, x)
// promise2 表示第二个 promise
nextPromise.resolveWith(x)
}
})
...
}

reject(reason) {
...
// 遍历 callbacks, 调用所有的 handle
this.callbacks.forEach(handle => {
const fail = handle[1]
const nextPromise = handle[2]
if (typeof fail === 'function') {
// 这里 reject 的逻辑也是一样的
const x = fail.call(undefined, reason)
nextPromise.resolveWith(x)
}
})
...
}
...
}

接下来就是 重头戏 了,因为要实现这一条规范,或者说通过上面的测试用例,还需要结合 规范 2.3.x 来看,因为 2.3.x 的规范是教你怎么写上面说到的 resolveWith 函数的

resolveWith

为了实现 resolveWith,必须遵循以下步骤(规范)

2.3.1

If promise and x refer to the same object, reject promise with a TypeError as the reason

如果 promise 和值 x 引用的是同一个对象,则用 TypeError 作为 reason 拒绝(reject)promise,因为之前 resolveWith 函数是通过 nextPromise.resolveWith(x) 这种方式调用的,所以这里规范说的 promise 就是指这个代码中的 nextPromise,而在 resolveWith 函数中,nextPromise 就是 this。即为了实现 2.3.1 规范, resolveWith 函数应该这么写

resolveWith(x) {
if (x === this) {
// 2.3.1
return this.reject(new TypeError())
}
}

2.3.2

如果 x 是一个 promise,采取它的状态

2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.
2.3.2.2 If/when x is fulfilled, fulfill promise with the same value.
2.3.2.3 If/when x is rejected, reject promise with the same reason.

上面的规范,说白了就是,如果 x 是一个 promise,就采取 promise 的状态,所以就在 resolveWith 函数中加判断条件

resolveWith(x) {
if (x === this) {
// 2.3.1
return this.reject(new TypeError())
} else if (x instanceof PromiseComplex) {
// 2.3.2
x.then(
result => this.resolve(result),
reason => this.reject(reason)
)
}
}

2.3.3

否则,如果 x 是一个对象或者函数

2.3.3.1 Let then be x.then.

这句话的意思就是代码,即声明一个 then 变量,将 x.then 赋值给这个变量

2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.

如果 x.then 抛出一个异常,则使用 e 作为 reason 并 reject 这个 promise

2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:

如果 then 是一个方法,把 x 当作 this 来调用它, 第一个参数为 resolvePromise,第二个参数为 rejectPromise

2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).

如果 resolvePromise 调用时传了参数 y,则执行 resolveWith(y)

2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r

如果 rejectPromise 调用时传了参数 r,则执行 resolveWith(r)(这里也可以直接调用 this.reject(r) 函数,为了程序的统一性,这边改动成了 resolveWith(r))

2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored

如果 resolvePromiserejectPromise 都被调用了,或者对同一个参数进行多次调用,则以第一次调用为优先级最高,其他未来的调用会被忽略掉。这里解释下这个情况,就是比方说如下的代码,可以说明 2.3.3.3.3 中所说的例子

const promise = new PromiseComplex((resolve, reject) => resolve({
then: (resolve, reject) => {
resolve('123')
reject('456')
}
}))

promise.then(res => console.log(res)) // '123'

或者

const promise = new PromiseComplex((resolve, reject) => resolve({
then: (resolve, reject) => {
resolve('123')
resolve('456')
resolve('789')
}
}))

promise.then(res => console.log(res)) // '123'

或者

const promise = new PromiseComplex((resolve, reject) => {
PromiseComplex.resolve('123').then(result => resolve(result))
PromiseComplex.resolve('456').then(result => resolve(result))
})

promise.then(res => console.log(res)) // '123'

上面的代码,多次进行了 resolve 的操作,但是只取第一次的,所以最终打印出来的就是 '123'。由于该功能已经在上一篇博客文章中解决,所以这里的规则可以自动忽略掉

2.3.3.3.4 if calling then throws an exception e
2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
2.3.3.3.4.2 Otherwise, reject promise with e as the reason.

如果调用 then 的时候抛出异常了

  • 如果 resolvePromiserejectPromise 的都被调用了,就忽略它
  • 否则,reject 掉这个 promise

2.3.3.4 If then is not a function, fulfill promise with x.

如果 then 不是一个函数,则 resolve 掉这个 promise

根据以上的描述,resolveWith 中的代码可以增加为

resolveWith(x) {
if (x === this) {
// 2.3.1
return this.reject(new TypeError())
} else if (x instanceof PromiseComplex) {
// 2.3.2
x.then(
result => this.resolve(result),
reason => this.reject(reason)
)
} else if (x instanceof Object) {
// 2.3.3 另外,如果 x 是个对象或者方法
let then
try {
// 2.3.3.1
then = x.then
} catch (e) {
// 2.3.3.2 如果取回的 x.then 属性的结果为一个异常 e,用 e 作为原因 reject promise
return this.reject(e)
}
// 2.3.3.3 如果 then 是一个方法,把 x 当作 this 来调用它, 第一个参数为 resolvePromise,第二个参数为 rejectPromise
if (then instanceof Function) {
try {
// 2.3.3.3.1/2.3.3.3.2/2.3.3.3.3
then.call(x, y => this.resolveWith(y), r => this.resolveWith(r))
} catch (e) {
// 2.3.3.3.4
this.reject(e)
}
} else {
// 2.3.3.4 如果 then 不是一个函数,用 x 完成 (fulfill) promise
this.resolve(x)
}
}
}

2.2.7.2

2.2.7.2 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.

如果在调用 resolve 或者 reject 时抛出了异常,则 nextPromise 必须调用 reject,并将这个 e 作为参数传入

测试用例

it('2.2.7.2 如果 success 或 fail 抛出一个异常 e, promise2 必须被拒绝', done => {
const promise = new Promise(resolve => {
resolve()
})
const error = new Error()
const fn = sinon.fake()
promise
.then(() => { throw error })
.then(null, fn)
setTimeout(() => {
assert(fn.called)
assert(fn.calledWith(error))
done()
}, 0)
})

源码

由规范的定义可知,这里就需要在 resolve 以及 reject 函数中,在调用成功或者失败函数时,需要加个 try catch,因为在调用这些函数的时候,有可能会失败

class PromiseComplex {
...
resolve(result) {
...
if (typeof succeed === 'function') {
// 这里调用的时候有可能报错
let x
try {
x = succeed.call(undefined, result)
} catch (e) {
// 2.2.7.2 如果onFulfilled或onRejected抛出一个异常e
// promise2 必须被拒绝(rejected)并把e当作原因
return nextPromise.reject(e)
}
nextPromise.resolveWith(x)
}
...
},
reject(reason) {
...
if (typeof fail === 'function') {
let x
try {
x = fail.call(undefined, reason)
} catch (e) {
return nextPromise.reject(e)
}
nextPromise.resolveWith(x)
}
...
},
}

API 实现

以上,基本就是实现了一个复杂的符合 Promises/A+ 规范的 Promise 了,接下来就是实现该 Promise 的其他 API 的工作了

resolve/reject

真正的 Promise 中是能直接访问到这个方法的,而我们这里实现的 PromiseComplex 是一个类,如果要能访问到这个方法,则必须在方法上加上 static 属性

测试用例

这里另起一个 describe,专门用来测试该 Promise 的 API 用。之后的各种 API 相关的测试用例都在这个里面写

describe('Promise API', () => {
it('测试 resolve', done => {
const promise = Promise.resolve2(123)
assert(promise instanceof Promise)
promise.then(result => {
assert(result === 123)
done()
})
const promise2 = Promise.resolve2(new Promise(() => {}))
assert(promise2 instanceof Promise)
})
it('测试 resolve thenable', done => {
const promise2 = Promise.resolve2({
then(resolve, reject) { resolve('233') }
})
assert(promise2 instanceof Promise)
promise2.then(result => {
assert(result === '233')
done()
})
})
it('测试 reject', done => {
const promise = Promise.reject2(123)
assert(promise instanceof Promise)
promise.then(null, result => {
assert(result === 123)
done()
})

// reject thenable
const promise2 = Promise.reject2({
then(resolve, reject) { reject('233') }
})
assert(promise2 instanceof Promise)
promise2.then(null, result => {
assert(result === '233')
done()
})
})
})

源码

简便起见,这里直接使用 resolve2reject2 来命名这两个方法

class PromiseComplex {
...
static resolve2(result) {
return new PromiseComplex((resolve, reject) => {
if (result && result.then && typeof result.then === 'function') {
result.then(resolve, reject)
} else {
resolve(result)
}
})
}

static reject2(reason) {
return new PromiseComplex((resolve, reject) => {
reject(reason)
})
}
...
}

可以看到,基本上就是调用了类里面的逻辑,new 一个 Promise,然后调用里面的 resolve 或者 reject 方法。但是对于 resolve 来说,如果 result 中有 then,并且这个 then 是一个函数,则需要继续调用这个 result.then

catch

用来捕获 Promise reject 的错误用的,这里实现的逻辑也很简单

测试用例

it('测试 catch', done => {
const promise = new Promise(resolve => {
resolve()
})
const fn = sinon.fake()
promise
.then(result => { throw '233' })
.catch((e) => { assert(e === '233') })
.then(fn)

setTimeout(() => {
assert(fn.called)
done()
}, 0)
})

源码

class PromiseComplex {
...
catch(reject) {
return this.then(null, reject)
}
...
}

注意这里 catch 的参数 reject 是一个失败函数

finally

测试 finally 这个 API 的关键就是看 finally 中传的函数有没有得到执行,所以测试用例可以写成下面这样

测试用例

it('测试 finally', () => {
const promise = new Promise(resolve => {
resolve()
})
const callbacks = [sinon.fake(), sinon.fake(), sinon.fake()]
promise
.then(() => { throw 'error' })
.catch(callbacks[0])
.finally(callbacks[1])
.then(callbacks[2])

setTimeout(() => {
assert(callbacks[0].called)
assert(callbacks[1].called) // 测试被调用
assert(callbacks[2].called)
assert(callbacks[2].calledAfter(callbacks[1]))
}, 0)
})

源码

因为无论成功或者失败,都会走到 finally 中,所以 finally 都可以继续 then

class PromiseComplex {
...
finally(callback) {
return this.then(
(result) => {
return PromiseComplex.resolve2(callback()).then(() => result)
},
(error) => {
return PromiseComplex.resolve2(callback()).then(() => {throw error})
}
)
}
...
}

这里有个疑问,为何 finally 不能做如下的实现

finally(callback) {
return this.then(
callback,
callback
)
}

是因为假如我们想要再其调用失败函数的时候报错,比如需要做一个 throw an error 之类的操作,这个时候你只能通过 Promise.resolve 之类的方式去实现它。而为了能够跟失败函数被调用时代码逻辑一致,所以成功函数也写成跟失败函数类似的逻辑。

all

all API 的逻辑看起来简单,就是遍历执行,但是内部是做了一些情况的判断的,不过测试用例还是很简单的,如下

测试用例

it('测试 all, 等待所有都完成(或第一个失败) ', done => {
const promise1 = Promise.resolve2(3)
const promise2 = 1337
const promise3 = new Promise((resolve, reject) => {
resolve('foo')
})
let ret
Promise.all([promise1, promise2, promise3]).then(results => {
ret = results
})
setTimeout(() => {
assert(ret[0] === 3)
assert(ret[1] === 1337)
assert(ret[2] === 'foo')
done()
}, 10)
})

源码

先说下实现 all API 需要注意的几个点

  • 如果传入的参数是一个空的可迭代对象,那么此 promise 对象回调完成(resolve), 只有此情况是同步执行的,其它都是异步返回
  • promises 中所有的 promise 都“完成”时,或参数中(result)不包含 promise 时回调完成。
  • 如果传入的参数(promises[i])不包含任何 promise,则返回一个异步完成
  • 如果参数中有一个 promise 失败,那么 Promise.all 返回的 promise 对象失败
  • 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组

所以实现的逻辑如下

class PromiseComplex {
...
static all(promises) {
return new PromiseComplex((resolve, reject) => {
let index = 0
// 5. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
let resultArray = []
// 1. 如果传入的参数是一个空的可迭代对象,
// 那么此promise对象回调完成(resolve),
// 只有此情况,是同步执行的,其它都是异步返回
if (promises && promises.length === 0) {
return resolve(resultArray)
}

const processResult = (result, i) => {
resultArray[i] = result
// 2. promises 中所有的promise都“完成”时
// 或参数中(result)不包含 promise 时回调完成。
if (++index === promises.length) resolve(resultArray)
}
for (let i = 0; i < promises.length; i++) {
// 3. 如果传入的参数(promises[i])不包含任何 promise,则返回一个异步完成
PromiseComplex.resolve2(promises[i])
.then(
result => processResult(result, i),
// 4. 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败
reason => reject(reason)
)
}
})
}
...
}

allSettled

allSettled API 在 MDN 的解释如下

The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.

Promise.allSettled 方法会返回一个 promise,then 中传的函数的参数就是一个对象数组,这个对象数组描述了每个 promise 的输出情况,这个 promise 能够 resolve 所有给到的 promises,无论这些 promises 是被 fulfilled 了还是被 rejected

比如如下的代码

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result)));

输出就是如下

> Object { status: "fulfilled", value: 3 }
> Object { status: "rejected", reason: "foo" }

测试用例

it('测试 allSettled', done => {
const promise1 = Promise.resolve2(3)
const promise2 = new Promise((resolve, reject) => reject('foo'))
const promises = [promise1, promise2]

let ret
Promise.allSettled(promises).then(results => {
ret = results
})
setTimeout(() => {
assert(ret[0].state === 'fulfilled' && ret[0].result === 3)
assert(ret[1].state === 'rejected' && ret[1].reason === 'foo')
done()
}, 10)
})

源码

源码的逻辑就是在 all API 的基础上做修改,注意这里返回的状态

  • 如果成功,那么每个 promise 产出的结果应该是 { state: 'fulfilled', result: 123 }
  • 如果失败,那么每个 promise 产出的结果应该是 { state: 'rejected', reason: 456 }

所以应该这么写

class PromiseComplex {
...
static allSettled(promises) {
return new PromiseComplex((resolve, reject) => {
let index = 0
let resultArray = []
if (promises && promises.length === 0) return resolve(resultArray)
// 这里的逻辑需要对 state 做个判断
const processResult = (result, i, state) => {
resultArray[i] = status === 'fulfilled'
? {status, result}
: {status, reason: result}
if (++index === promises.length) resolve(resultArray)
}
for (let i = 0; i < promises.length; i++) {
PromiseComplex.resolve2(promises[i])
.then(
result => processResult(result, i, 'fulfilled'),
// 无论成功或者失败,都调用 resolve
reason => processResult(reason, i, 'rejected')
)
}
})
}
...
}

但是这里其实还有一种更加优雅的写法,直接调用 Promise.all 的形式来实现

static allSettled(promises) {
const x = (promises) => promises.map(
promise => promise.then(
value => ({status: 'fulfilled', value}),
reason => ({status: 'rejected', reason})
)
)
return PromiseComplex.all(x(promises))
}

这里将 promises 做了一个 map,然后它返回的是一个数组 x(promises),代码就变得更加精简和易读了。你可能会迷惑,这里为啥要用到一个 promise.then 对其做一个值的构建呢?因为我们这里,then 是会返回一个 promise 的,通过使用 .then() 这种方式来修改 promise 中的传值参数才是一种符合 promise 的方式。

race

race 在 MDN 上的解释如下

The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.

Promise.race() 方法返回一个 promise,一旦其中一个 promise 处于一个可迭代的 fulfills 或者 rejects 中时,那这个对应的 promise 就会 fulfill 一个 value 出来或者 reject 一个 reason 出来

简单来说就是 竞争,哪个先执行 resolve 或者 reject,哪个就先导出对应的结果

测试用例

上面可能说的不是很清楚,先看下测试用例吧

it('测试 race', done => {
const promise1 = new Promise(
(resolve, reject) => setTimeout(() => resolve(200), 200)
)
const promise2 = new Promise(
(resolve, reject) => setTimeout(() => resolve(10), 10)
)
const promise3 = new Promise(
(resolve, reject) => setTimeout(() => reject(100), 100)
)
let result, reason
Promise.race([promise1, promise2, promise3])
.then(
data => result = data,
error => reason = error
)

setTimeout(() => {
assert(result === 10)
done()
}, 500)
})

以上的测试用例,由于 promise2 持续的时间最短,那么按照道理来说,promise2 的结果就是最终的结果

源码

race API 源码实现思路也是通过遍历来实现,但是这里是通过调用 Promise.resolve(xxx).then 来实现的。只要其中有一个 resolve 或者 reject 了,那么就自动then 的逻辑,其他的就忽略掉。

static race(promises) {
return new PromiseComplex((resolve, reject) => {
if (promises && promises.length === 0) return
for (let i = 0; i < promises.length; i++) {
PromiseComplex.resolve2(promises[i])
.then(
// 谁(promises[i]) 先完成谁先 resolve
result => resolve(result),
reason => reject(reason)
)
}
})
}

你可能会疑惑了,为啥这里可以自动走 then 的逻辑呢?这又牵扯到 微任务宏任务 的知识了。先说结论,在 eventLoop 中,一旦执行一个阶段里的一个宏任务(setTimeout, setInterval 和 setImmediate)就立刻执行微任务队列(Node 11 及以上或者浏览器环境中),而我们代码中的 resolve 方法(不是 resolve2 方法) 是放在微任务队列中的(下文优化代码部分会说到)。所以它会先执行 PromiseComplex.resolve2(promises[i])(宏任务),在下一个宏任务(promise2 中的 resolve)到来之前,执行微任务(resolve 中的代码逻辑),发现状态没有变(state === 'pending')就往下执行,将状态改变,并且将 nextTick 中的代码入微任务队列。然后时间到,执行宏任务(promise2 中的 resolve),然后执行微任务(resolve 中的代码逻辑),发现状态变了(state !== 'pending'),后面的逻辑就不执行了。这个时候因为你已经将 then 中的 callback 当作参数传给了 Promise 内部的变量,所以就可以拿着这些 callbacks 去执行。这样看起来就是 then 中的逻辑被 自动执行

你可能又会疑惑了,为啥这里可以忽略掉其他的 promise 呢?还记得规范 2.3.3.3.3 中说的是啥么(手动滑稽),以及结合规范 2.2.2 和 2.2.3 来看的话,关键的地方在于,这个 promise 内部的 state 改变了,一旦这个 state 被改变了,那么无论后面怎么 resolve 或者 reject,那么都会在判断 state 状态时被 return 掉

resolve (result) {
if (this.state !== 'pending') return
...
}

reject (reason) {
if (this.state !== 'pending') return
...
}

重构 resolve/reject API

在之前的这两个 api 的逻辑中,关于异步的部分只是用了 setTimeout 来解决的,但是这里就会存在一个 bug,在测试用例中,要断言 assert 测试代码的结果,也需要用到 setTimeout,但是这里需要延时一小段时间,不然会和源码中的 setTimeout 有冲突(即如果测试用例中 setTimeout 传的时间是 0 ms,那么有可能这个会优先源码中的 setTimeout 执行),这是一个 bug 隐患。那么解决方式是什么呢?很简单,将 setTimeout 修改成 process.nextTick(微任务,优先级更高) 即可

resolve(result) {
process.nextTick(() => {
...
})
}
reject(reason) {
process.nextTick(() => {
...
})
}

但是这样又会带来新的问题,如果是浏览器环境就不支持 process.nextTick,所以这里就需要想办法做一个兼容的处理。怎么兼容呢,这里可以参考 Vue 源码中关于 nextTick 是怎么处理的。这里方便起见就直接上代码了

// 兼容 process.nextTick 和 setImmediate 方案
// 其实就是 vue 里面的 nextTick 方案
// 主要是用 mutationObserver 实现的,这个只要改动下 dom 去更新一个函数
// 而在这个函数里面去做操作即可,这个是比 setTimeout 要快的
function nextTick(fn) {
// 兼容处理
if (process !== undefined && typeof process.nextTick === 'function') {
return process.nextTick(fn)
}

var counter = 1
var observer = new MutationObserver(fn)
var textNode = document.createTextNode(String(counter))

// 监听节点变化
observer.observe(textNode, { characterData: true })

// 修改节点
counter = (counter + 1) % 2
textNode.data = String(counter)
}

这里是通过 MutationObserver 这个 API,通过创建一个 textNode 的节点,然后使用 observer.observe监听这个节点的变化,节点一旦变化就会去执行传入的 fn 回调,这样就可以做一个兼容的处理了。

最后只需要将 resolve/reject API 中的 process.nextTick 改写成 nextTick 函数即可

总结

以上就是实现一个复杂的 Promise 的完整步骤了,仓库链接可以点击这里,另外我还在这个项目中对部分代码进行了优化,但总体上的逻辑和本文都是一样的,若有看到源码不一样的同学还请不要惊讶

本质上来说,这里主要就规范中的 2.2.7 以及 2.3 的部分进行了比较细的描述,如果有问题欢迎各位大佬在项目中提 issue