实现一个 Promise A+ 规范的 Promise
简介 Promise A+ 规范
变量和术语
- Promise 具有 3 种状态:pending、fulfilled、rejected,初始状态为 pending,切换为 fulfilled 或者 rejected 后就不能再转换。
ts- const testPromise = new Promise((resolve, reject) => {
- // DO SOMETHING
- })
-
像这样子,传入的函数我们称为
executor,resolve和reject会触发 Promise 的状态以及数据更新。 -
Promise A+ 规范的 Promise 上的方法只有简单的
then,catch、finally什么的都没有。 -
从 Promise A+ 的测试用例(写在后面噢)来看,可以使用
testPromise.resolve和testPromise.reject在实例化Promise之后手动修改它的状态和数据。
关于 then 方法
then方法具有onFulfilled和onRejected两个入参,返回一个 Promise(链式调用)。
ts- const temp = testPromise.then(function onFulfilled (value) {
- // DO SOMETHING
- }, function onRejected (reason) {
- // DO SOMETHING
- })
- console.log(temp instanceof Promise) // true
- console.log(temp === testPromise) // false
-
Promise 从 pending 状态切换到 fulfilled 或者 rejected 时,执行此前
then传入的onFulfilled或onRejected。fulfilled 状态的 Promise 会执行then传入的onFulfilled,rejected 状态的 Promise 会执行then传入的onRejected。 -
执行
onFulfilled或onRejected的结果会被传入新的 Promisetemp的resolve方法中,如果发生了错误则传入reject中,改变的状态和数据。 -
onFulfilled或者onRejected不是函数时,返回的 Promise 与原 Promise 具有相同的状态和数据(传值穿透)。 -
不支持我返回我自己,
onFulfilled或者onRejected返回该then返回的 Promise 时,抛出TypeError错误,例如:
ts- const temp = testPromise.then(function onFulfilled (value) {
- return temp
- })
关于兼容 thenable 对象
- thenable 的对象是具有
then方法的对象或者函数(typeof返回object或者function)。then方法接受两个回调函数onResolvePromise和onRejectPromise,类似于这里的 Promise 的then。thenable 实际上包括实现了 Promise A+ 规范的 Promise,例如 ES6 原生的 Promise。举个栗子:
ts- const thenable = {
- then: function (onResolvePromise) {
- onResolvePromise('miao~~')
- }
- }
-
如果触发了
onFulfilled,返回了一个 thenable。如果是该 Promise 的实例,不是当前 Promise,则传入onFulfilled和onRejected,调用 then 方法。 -
兼容其他 thenable 要求
onFulfilled的回调可以给 thenable 的then方法传入两个回调函数,像该 Promise 一样解析。 -
在解析 thenable 对象时(也就是
onResolvePromise或者onRejectPromise都没有被触发),新的onFulfilled或者onRejected触发将被忽略。此时,当前 Promise 是处于 pending 状态的,如果触发转向其他两个状态的改变也会被忽略(这一点主要是在测试用例中体现出来,看标准没怎么注意到)。 -
允许其他 thenable 对象乱写,这里需要处理 thenable 对象重复触发onResolvePromise或/和onRejectPromise的情况,这两个回调函数最多只能被调用各 1 次。
-
resolve和reject被触发时发生什么事了?此时 Promise 的状态仍未真正变化,会进入一段处理程序,规范称之为 Promise Resolution Procedure,主要逻辑是如果传入的是非 thenable 对象或者基本类型则直接修改 Promise 的状态和数据,是 thenable 就执行上述 thenable 相关逻辑。 -
其他详细见 Promise A+ 规范。
简单地写一个 Promise
ts- enum PromiseState {
- fulfilled = 'fulfilled',
- pending = 'pending',
- rejected = 'rejected'
- }
- type PromiseData = any
- type ThenCallback = any
- class ShikaPromise {
- data: PromiseData = undefined
- state: PromiseState = PromiseState.pending
- constructor (executor: Function) {
- // 如果报错了,Promise 处于 rejected 状态
- try {
- executor(this.resolve, this.reject)
- } catch(error) {
- this.reject(error)
- }
- }
- resolve (value: PromiseData) {
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.fulfilled
- this.data = value
- }
- }
- reject (reason: PromiseData) {
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.rejected
- this.data = reason
- }
- }
- then (onFulfilled: ThenCallback, onRejected: ThenCallback) {
- // TODO
- }
- }
下面就来写then方法实现异步的链式调用。
then 方法
then返回一个 Promise,虽然 Promise A+ 规范没有说明需要返回的 Promise 不能和原有的是同一个,但是考虑到后续链式调用也会涉及到 Promise 状态的改变,所以这里就返回一个新的 Promise。
假设const promise2 = promise1.then(onFulfilled, onRejected),调用promise1.then时创建一个新的 Promise promise2返回出去。用过 ES6 的Promise很好理解,如果原有promise1是 fulfilled 的,则在新的promise2的executor中的resolve传入onFulfilled的结果,如果promise1处于失败状态,rejected 了,则在promise2的resolve中传入onResolve的结果。
fulfilled 和 rejected 状态
ts- // ...
- class ShikaPromise {
- // ...
- private processFinish (
- resolve: Function,
- reject: Function,
- onFulfilledCallback: Function
- ) {
- process.nextTick(() => {
- try {
- const x = onFulfilledCallback(this.data)
- resolve(x)
- } catch (error) {
- reject(error)
- }
- })
- }
- then (onFulfilled?: ThenCallback, onRejected?: ThenCallback) {
- switch (this.state) {
- case PromiseState.fulfilled: {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- this.processFinish(
- resolve,
- reject,
- onFulfilledCallback
- )
- })
- }
- case PromiseState.rejected: {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- this.processFinish(
- resolve,
- reject,
- onRejectedCallback
- )
- })
- }
- default: {
- // TODO
- }
- }
- }
pending 状态
promise1在等待的时候,可以在promise1上新建两个属性onResolvedCallback、onRejectedCallback缓存给promise2触发resolve和reject的回调函数。promise2处于 pending 状态,promise1切换状态后触发这些回调函数,用来改变promise2的状态。
ts- // ...
- class ShikaPromise {
- // 记录 fulfilled 或者 rejected 后执行的回调函数,将会用于修改 then 方法返回的 Promise 的 data
- private onResolvedCallback: Function[] = []
- private onRejectedCallback: Function[] = []
- // ...
- resolve (value: PromiseData) {
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.fulfilled
- this.data = value
- // 执行所有传入的回调函数
- const len = this.onResolvedCallback.length
- for (let i = 0; i < len; i++) {
- this.onResolvedCallback[i](value)
- }
- }
- }
- reject (reason: PromiseData) {
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.rejected
- this.data = reason
- // 执行所有传入的回调函数
- const len = this.onRejectedCallback.length
- for (let i = 0; i < len; i++) {
- this.onRejectedCallback[i](reason)
- }
- }
- }
- // ...
- then (onFulfilled?: ThenCallback, onRejected?: ThenCallback) {
- switch (this.state) {
- case PromiseState.fulfilled: {
- // ...
- }
- case PromiseState.rejected: {
- // ...
- }
- default: {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- this.onResolvedCallback.push(() => {
- this.processFinish(
- resolve,
- reject,
- onFulfilledCallback
- )
- })
- this.onRejectedCallback.push(() => {
- this.processFinish(
- resolve,
- reject,
- onRejectedCallback
- )
- })
- })
- }
- }
- }
- }
如何处理 Promise Resolution Procedure
resolve 和 reject 包含 Promise Resolution Procedure 逻辑
上面说到,resolve、reject会进入 Promise Resolution Procedure 逻辑,这里先把resolve、reject改造一下:
ts- // ...
- class ShikaPromise {
- // ...
- private resolveData (
- resolve: Function,
- reject: Function,
- data: PromiseData,
- // 因为 resolve 卡住时 Promise 状态是不能改变的,因此把 reject 情况也统一处理
- targetState: PromiseState.fulfilled | PromiseState.rejected
- ) {
- // TODO
- }
- resolve (value: PromiseData) {
- this.resolveData(
- this.processResolve.bind(this),
- this.processReject.bind(this),
- value,
- PromiseState.fulfilled
- )
- }
- reject (reason: PromiseData) {
- this.resolveData(
- this.processResolve.bind(this),
- this.processReject.bind(this),
- reason,
- PromiseState.rejected
- )
- }
- // 原有的 resolve 和 reject
- private processResolve (value: PromiseData) {
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.fulfilled
- this.data = value
- const len = this.onResolvedCallback.length
- for (let i = 0; i < len; i++) {
- this.onResolvedCallback[i](value)
- }
- }
- }
- private processReject (reason: PromiseData) {
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.rejected
- this.data = reason
- const len = this.onRejectedCallback.length
- for (let i = 0; i < len; i++) {
- this.onRejectedCallback[i](reason)
- }
- }
- }
- // ...
- }
防止等待时多次触发
resolve卡住时要保持状态不变和不能触发 thenable 的回调,这里加一个锁?:
ts- // ...
- class ShikaPromise {
- // ...
- // 用于下面记录 thenable 回调触发情况
- private dataOnFulfilledCalled = false
- private dataOnRejectedCalled = false
- // 这里的?针对 thenable 回调没有触发卡住的情况,thenable 回调会传入什么值我们是不知道的
- // 所以用产生 thenable 的 data 来锁一下
- private innerThenablePending: PromiseData[] = []
-
- private resolveData (
- resolve: Function,
- reject: Function,
- data: PromiseData,
- preData: PromiseData,
- targetState: PromiseState.fulfilled | PromiseState.rejected
- ) {
- if (this.innerThenablePending.length === 0 || this.innerThenablePending[0] === preData) {
- this.innerThenablePending = [data]
- if (targetState === PromiseState.fulfilled) {
- // TODO
- } else if (targetState === PromiseState.rejected) {
- // reject 的情况下不涉及对 thenable 的处理,直接扔出去
- reject(data)
- }
- }
- }
- resolve (value: PromiseData) {
- this.resolveData(
- this.processResolve.bind(this),
- this.processReject.bind(this),
- value,
- undefined,
- PromiseState.fulfilled
- )
- }
- reject (reason: PromiseData) {
- this.resolveData(
- this.processResolve.bind(this),
- this.processReject.bind(this),
- reason,
- undefined,
- PromiseState.rejected
- )
- }
- private processResolve (value: PromiseData) {
- // 触发了状态改变说明了已经完成了一次 onFulfilled 或者 onRejected 的调用,把锁扔掉
- innerThenablePending = []
- // ...
- }
- private processReject (reason: PromiseData) {
- innerThenablePending = []
- // ...
- }
- // ...
- }
解析 thenable
接下来就是resolve的情况下 Promise Resolution Procedure 的逻辑:
ts- class ShikaPromise {
- // ...
- private dataOnFulfilledCalled = false
- private dataOnRejectedCalled = false
- private innerThenablePending: PromiseData[] = []
-
- private resolveData (
- resolve: Function,
- reject: Function,
- data: PromiseData,
- preData: PromiseData,
- targetState: PromiseState.fulfilled | PromiseState.rejected
- ) {
- if (this.innerThenablePending.length === 0 || this.innerThenablePending[0] === preData) {
- this.innerThenablePending = [data]
- if (targetState === PromiseState.fulfilled) {
- if (this === data) {
- // 不能我自己返回我自己
- reject(new TypeError('Cannot resolve self!'))
- } else if (data instanceof ShikaPromise) {
- // ShikaPromise,直接执行 then 方法
- try {
- data.then(resolve, reject)
- } catch (error) {
- reject(error)
- }
- } else if (typeof data === 'object' || typeof data === 'function') {
- // 其他 thenable 的逻辑
- let thenMethod: Function
- try {
- // 规范里面 data.then 只能取一次
- thenMethod = data?.then
- } catch (error) {
- reject(error)
- return
- }
-
- if (typeof thenMethod === 'function') {
- try {
- thenMethod.call(data, (y: PromiseData) => {
- // onFulfilledCalled、onRejectedCalled 只能最多各调用一次
- if (
- (!this.dataOnFulfilledCalled || !this.dataOnRejectedCalled)
- ) {
- this.dataOnFulfilledCalled = true
- // 递归直至 thenable 解析完
- this.resolveData(
- resolve,
- reject,
- y,
- data,
- PromiseState.fulfilled
- )
- }
- }, (y: PromiseData) => {
- if (
- (!this.dataOnFulfilledCalled || !this.dataOnRejectedCalled)
- ) {
- this.dataOnRejectedCalled = true
- // 错误的,直接扔出去,调用 resolveData 主要是处理已有 thenable 待解析的情况
- this.resolveData(
- resolve,
- reject,
- y,
- data,
- PromiseState.rejected
- )
- }
- })
- } catch (error) {
- // 错误的,直接扔出去,调用 resolveData 主要是处理已有 thenable 待解析的情况
- this.resolveData(
- resolve,
- reject,
- error,
- data,
- PromiseState.rejected
- )
- }
- } else {
- // 有 then 但不是方法
- resolve(data)
- }
- } else {
- // 不是 object 或者 function
- resolve(data)
- }
- } else if (targetState === PromiseState.rejected) {
- reject(data)
- }
- }
- }
- // ...
- }
其他方法
JS 的 Promise
下面就来实现一下 JS 的 Promse 的catch和finally。catch就是then方法只提供第二个参数。finally方法回调函数不接收任何参数,返回一个状态和数据与原来相同的 Promise。
ts- // ...
- class ShikaPromise {
- // ...
- catch (onRejected: Function) {
- return this.then(undefined, onRejected)
- }
- finally (onFinally: Function) {
- const onFulfilled = value => {
- onFinally()
- return value
- }
- const onRejected = reason => {
- onFinally()
- throw reason
- }
- return this.then(onFulfilled, onRejected)
- }
- // ...
- }
还有Promise.resolve和Promise.reject两个静态方法:
ts- // ...
- class ShikaPromise {
- // ...
- static resolve (value?: PromiseData) {
- return new ShikaPromise((resolve: Function) => {
- resolve(value)
- })
- }
- static reject (reason?: PromiseData) {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- reject(reason)
- })
- }
- // ...
- }
如果 Promise 可以停止
如果想要 Promise 后面的then(catch、finally)都不会触发,这里只需要返回一个 pending 状态的 Promise。这里实现一个时链式调用停止的cancel方法和返回 pending 的 Promise 的wait方法:
ts- // ...
- class ShikaPromise {
- // ...
- cancel (value?: PromiseData) {
- return this.then(() => new ShikaPromise(() => {}), () => new ShikaPromise(() => {}))
- }
- static wait () {
- return new ShikaPromise(() => {})
- }
- // ...
- }
Promise A+ 测试
下载 promises-aplus-tests 包:
cmd- npm i promises-aplus-tests
要求 Promise 所在文件采用 commonjs 方式导出。还需要在 Promise 上实现静态方法:
ts- // ...
- class ShikaPromise {
- // ...
- static deferred () {
- const testPromise = new ShikaPromise(() => {})
- return {
- promise: testPromise,
- resolve: testPromise.resolve.bind(testPromise),
- reject: testPromise.reject.bind(testPromise),
- }
- }
- // ...
- }
promises-aplus-tests Promis 所在文件即可运行,如果你在用 TS,文件为编译后的文件,例如:
cmd- promises-aplus-tests dist/ShikaPromise.js
Promise A+ 的测试用例覆盖面非常全,调试时烦死了x,通过了所有 817 条用例,就说明你的 Promise 实现了 Promise A+ 标准了。
我把 TS 编译和运行测试用例在 package.json 组装成一条命令:
json- {
- // ...
- "scripts": {
- // ...
- "test": "tsc && promises-aplus-tests dist/ShikaPromise.js",
- },
- // ...
- }
这里 tsc 会默认编译 tsconfig.json 设置的根目录(这里是 ./src),然后放到输出目录中(这里是 ./dist)。
最终实现
ts- enum PromiseState {
- fulfilled = 'fulfilled',
- pending = 'pending',
- rejected = 'rejected'
- }
- type PromiseData = any
- type ThenCallback = any
- class ShikaPromise {
- // 记录 fulfilled 或者 rejected 后执行的回调函数,将会用于修改 then 方法返回的 Promise 的 data
- private onResolvedCallback: Function[] = []
- private onRejectedCallback: Function[] = []
- data: PromiseData = undefined
- state: PromiseState = PromiseState.pending
- constructor (executor: Function) {
- try {
- executor(this.resolve.bind(this), this.reject.bind(this))
- } catch(error) {
- this.reject(error)
- }
- }
- private dataOnFulfilledCalled = false
- private dataOnRejectedCalled = false
- private innerThenablePending: PromiseData[] = []
- private resolveData (
- resolve: Function,
- reject: Function,
- data: PromiseData,
- preData: PromiseData = undefined,
- targetState: PromiseState.fulfilled | PromiseState.rejected
- ) {
- if (this.innerThenablePending.length === 0 || this.innerThenablePending[0] === preData) {
- this.innerThenablePending = [data]
- if (targetState === PromiseState.fulfilled) {
- if (this === data) {
- reject(new TypeError('Cannot resolve self!'))
- } else if (data instanceof ShikaPromise) {
- try {
- data.then(resolve, reject)
- } catch (error) {
- reject(error)
- }
- } else if (typeof data === 'object' || typeof data === 'function') {
- let thenMethod: Function
- try {
- thenMethod = data?.then
- } catch (error) {
- reject(error)
- return
- }
-
- if (typeof thenMethod === 'function') {
- try {
- thenMethod.call(data, (y: PromiseData) => {
- if (
- (!this.dataOnFulfilledCalled || !this.dataOnRejectedCalled)
- ) {
- this.dataOnFulfilledCalled = true
-
- this.resolveData(
- resolve,
- reject,
- y,
- data,
- PromiseState.fulfilled
- )
- }
- }, (y: PromiseData) => {
- if (
- (!this.dataOnFulfilledCalled || !this.dataOnRejectedCalled)
- ) {
- this.dataOnRejectedCalled = true
- this.resolveData(
- resolve,
- reject,
- y,
- data,
- PromiseState.rejected
- )
- }
- })
- } catch (error) {
- this.resolveData(
- resolve,
- reject,
- error,
- data,
- PromiseState.rejected
- )
- }
- } else {
- resolve(data)
- }
- } else {
- resolve(data)
- }
- } else if (targetState === PromiseState.rejected) {
- reject(data)
- }
- }
- }
- resolve (value: PromiseData) {
- this.resolveData(
- this.processResolve.bind(this),
- this.processReject.bind(this),
- value,
- undefined,
- PromiseState.fulfilled
- )
- }
- reject (reason: PromiseData) {
- this.resolveData(
- this.processResolve.bind(this),
- this.processReject.bind(this),
- reason,
- undefined,
- PromiseState.rejected
- )
- }
- private processResolve (value: PromiseData) {
- this.innerThenablePending = []
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.fulfilled
- this.data = value
- const len = this.onResolvedCallback.length
- for (let i = 0; i < len; i++) {
- this.onResolvedCallback[i](value)
- }
- }
- }
- private processReject (reason: PromiseData) {
- this.innerThenablePending = []
- if (this.state === PromiseState.pending) {
- this.state = PromiseState.rejected
- this.data = reason
- const len = this.onRejectedCallback.length
- for (let i = 0; i < len; i++) {
- this.onRejectedCallback[i](reason)
- }
- }
- }
- private processFinish (
- resolve: Function,
- reject: Function,
- onFulfilledCallback: Function
- ) {
- process.nextTick(() => {
- try {
- const x = onFulfilledCallback(this.data)
- resolve(x)
- } catch (error) {
- reject(error)
- }
- })
- }
- then (onFulfilled?: ThenCallback, onRejected?: ThenCallback) {
- const onFulfilledCallback = typeof onFulfilled !== 'function'
- ? (value: PromiseData) => value
- : onFulfilled
- const onRejectedCallback = typeof onRejected !== 'function'
- ? (reason: PromiseData) => {
- throw reason
- }
- : onRejected
- switch (this.state) {
- case PromiseState.fulfilled: {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- this.processFinish(
- resolve,
- reject,
- onFulfilledCallback
- )
- })
- }
- case PromiseState.rejected: {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- this.processFinish(
- resolve,
- reject,
- onRejectedCallback
- )
- })
- }
- default: {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- this.onResolvedCallback.push(() => {
- this.processFinish(
- resolve,
- reject,
- onFulfilledCallback
- )
- })
- this.onRejectedCallback.push(() => {
- this.processFinish(
- resolve,
- reject,
- onRejectedCallback
- )
- })
- })
- }
- }
- }
- catch (onRejected: Function) {
- return this.then(undefined, onRejected)
- }
- finally (onFinally: Function) {
- const onFulfilled = value => {
- onFinally()
- return value
- }
- const onRejected = reason => {
- onFinally()
- throw reason
- }
- return this.then(onFulfilled, onRejected)
- }
- static resolve (value?: PromiseData) {
- return new ShikaPromise((resolve: Function) => {
- resolve(value)
- })
- }
- static cancel (value?: PromiseData) {
- return this.then(() => new ShikaPromise(() => {}), () => new ShikaPromise(() => {}))
- }
- static reject (reason?: PromiseData) {
- return new ShikaPromise((resolve: Function, reject: Function) => {
- reject(reason)
- })
- }
- static wait () {
- return new ShikaPromise(() => {})
- }
- static deferred () {
- const testPromise = new ShikaPromise(() => {})
- return {
- promise: testPromise,
- resolve: testPromise.resolve.bind(testPromise),
- reject: testPromise.reject.bind(testPromise),
- }
- }
- }
结尾
这里实现了一个 Promise A+ 规范的 Promise。写了好多啊,就不写结尾了吧。
