文章类型:个人学习笔记

手写 Promise 的前提是了解 Promise/A+ 规范,不说倒背如流,但是基本的点还是需要掌握的。

1.Promise/A+规范

原文地址:Promise/A+规范

提醒:翻译是采用个人理解+谷歌翻译逐字逐句翻译

(1)术语:

1.1. “promise” 是一个带有符合此行为规范 then 方法的对象(object)或函数(function)。

1.2. “thenable” 是定义 then 方法的一个对象(object)或函数(function)。

1.3. “value” 可以是任何合法JavaScript 值(包括 undefinedthenablepromise)。

1.4. “exception” 是被 throw 语句抛出的值。

1.5. “reason” 是表明 promise 为什么被拒绝的值。

(2)规范

2.1. Promise状态

一个 promise 必须处于以下三种状态之一:pendingfulFilled,或 rejected

2.1.1.promisepending 状态时:

  • 2.1.1.1. 可能会转换为 fulFilled状态rejected状态

2.1.2.promisefulFilled 状态时:

  • 2.1.2.1. 不能转换为任何其它状态。

  • 2.1.2.2. 必须有一个 value 值,且该 value 值不能更改。

2.1.3.promiserejected 状态时:

  • 2.1.3.1. 不能转换为任何其它状态。

  • 2.1.3.2. 必须有一个 reason 值,且该 reason 值不能更改。

在此,“不能更改”意味着恒等不变(即:===),但并不表示更深层次的不可改变。

2.2. then方法

一个 Promise 必须提供一个 then 方法用来访问 当前值、最终的 value值reason

一个 Promisethen 方法接收两个参数:

promise.then(onFulfilled, onRejected)

2.2.1. onFulfilledonRejected 都是可选参数:

  • 2.2.1.1. 如果 onFulfilled 不是函数,则必须将其忽略。

  • 2.2.1.2. 如果 onRejected 不是函数,则必须将其忽略。

2.2.2. 如果 onFulfilled 是一个函数:

  • 2.2.2.1. 必须在 promise 实现 fulFilled 状态后调用它,它的第一个参数是 promise 的值。

  • 2.2.2.2. 不能在 promise 实现 fulFilled 状态前调用它。

  • 2.2.2.3. 它不能被调用超过一次

2.2.3. 如果 onRejected 是一个函数:

  • 2.2.3.1. 必须在 promise 实现 rejected 状态后调用它,它的第一个参数是 reason

  • 2.2.3.2. 不能在 promise 实现 rejected 状态前调用它。

  • 2.2.3.3. 它不能被调用超过一次

2.2.4. onFulfilledonRejected 不能在执行上下文堆栈仅包含平台代码之前调用。[3.1]

2.2.5. onFulfilledonRejected 必须作为函数调用(即 没有 this 值)。[3.2]

2.2.6. then 可能在同一个 promise 中被多次调用。

  • 2.2.6.1. 如果当 promisefulFilled 状态时,所有 then 各自的 onFulfilled 回调必须按照其被 then 发起调用的顺序执行。

  • 2.2.6.2. 如果当 promiserejected 状态时,所有 then 各自的 onRejected 回调必须按照其被 then 发起调用的顺序执行。

2.2.7. then 必须返回一个 promise [3.3]

promise2 = promise1.then(onFulfilled, onRejected);
  • 2.2.7.1. 如果 onFulfilledonRejected 返回一个 value x,运行 promise 解决程序:
[[Resolve]](promise2, x)
  • 2.2.7.2. 如果 onFulfilledonRejected 抛出异常 epromise2 必须执行 rejectede 作为 reason 的值。

  • 2.2.7.3. 如果 onFulfilled 不是一个函数并且 promise1fulFilled 状态,promise2 必须是 fulFilled 状态且带着 promise1 同样的值。

  • 2.2.7.4. 如果 onRejected 不是一个函数并且 promise1rejected 状态,promise2 必须是 rejected 状态且带着 promise1 同样的 reason

2.3. Promise 执行程序 (解决程序)

Promise 解决程序是一个抽象操作表现为输入一个 promise 和一个 value值,我们用 [[Resolve]](promise, x) 表示,如果 x 是一个 thenable,它试图使 promise 采用 x 的状态,假设 x 的行为和 promise 类似。否则,它带着一个 value x 使 promise 转为 fulfilled 状态。

这种对于 thenable 的处理就允许 promise 实现互操作,只要他们暴露一个符合 Promises/A+then 方法。它也允许Promises/A+ 实现使用合理的 then 方法“整合”不合格的实现。

要运行 [[Resolve]](promise,x),请执行以下步骤:

2.3.1. 如果 promisex 指向同一个对象,promise 带着一个 TypeError 作为 reason 执行 reject

2.3.2. 如果 x 是一个 promise ,采用它的状态 [3.4]

  • 2.3.2.1。 如果 xpending 状态,promise 必须保持 pending 状态直到 x 是 fulFilled 状态或 rejected 状态。

  • 2.3.2.2. 如果当 x 是 fulfilled 状态,带着同样的 valuepromise 变成 fulFilled 状态。

  • 2.3.2.3. 如果当 x 是 rejected 状态,带着同样的 reasonpromise 变成 rejected 状态。

2.3.3. 除此之外,如果 x 是一个对象或函数:

  • 2.3.3.1. 将 x.then 赋值给 then[3.5]

  • 2.3.3.2. 如果使用属性 x.then 引发抛出异常 e,带着 e 作为 reasonpromise 变成 rejected 状态。

  • 2.3.3.3. 如果 then 是一个函数,用 x 作为 this 调用它,第一个参数 resolvePromise,第二个参数 rejectPromise,如下:

    • 2.3.3.3.1. 如果当 resolvePromise 带着一个 value y 被调用,执行 [[Resolve]](promise, y)
    • 2.3.3.3.2. 如果当 rejectPromise 带着一个 reason r 被调用,带着 r 作为 reasonpromise 变成 rejected 状态。
    • 2.3.3.3.3. 如果同时调用 resolvePromiserejectPromise ,或者对同一参数进行了多次调用,则第一个调用优先,而所有其他调用均被忽略。
    • 2.3.3.3.4. 如果调用 then 抛出异常 e
      • 2.3.3.3.4.1. 如果 resolvePromiserejectPromise已经被调用,请忽略它。
      • 2.3.3.3.4.2. 否则,带着 e 作为 reasonpromise 变成 rejected 状态。

2.3.4. 如果 then 不是一个函数,带着 x 作为 valuepromise 变成 fulFilled 状态。

如果一个 Promiseresolved 带着一个 thenable 在一个 thenable 循环链中,这样 [[Resolve]](promise, thenable) 的递归性质最终导致 [[Resolve]](promise, thenable) 再次被调用,遵循上述算法将导致无限递归,鼓励但不是必需的实现,以检测此类递归并以信息 TypeErrorreason 的执行 promisereject[3.6]

(3)附注

3.1. 这里的“平台代码”是指引擎,执行环境和 promise 实现代码。实际上,此要求可确保在事件循环回合之后调用 onFulfilledonRejected 异步执行,然后使用新的堆栈进行调用。可以使用 “宏任务” 机制(如 setTimeoutsetImmediate )或 “微任务” 机制(如 MutationObserverprocess.nextTick )来实现。由于 promise 实现被视为平台代码,因此它本身可能包含一个任务调度队列或“trampoline”,在其中调用处理程序。

3.2. 也就是说,在严格模式下,thisundefined 。 在非严格模式下,this 是全局对象。

3.3. 如果实现满足所有要求,则实现可以允许 promise2 === promise1 。 每个实现都应记录是否可以产生 promise2 === promise1 以及在什么条件下产生。

3.4. 通常,只有 x 来自当前的实现,才知道它是一个真正的 promise 。 本节允许使用特定的实现的方式来采用已知符合 promise 的状态。

3.5. 首先存储对 x.then 的引用,然后测试该引用,然后调用该引用的过程避免了对 x.then 属性的多次访问。 此类预防措施对于确保访问者属性的一致性非常重要,因为访问者属性的值在两次检索之间可能会发生变化。

3.6. 实现不应在可建立链的深度上设置任意限制,并假定超出该任意限制,则递归将是无限的。 只有真正的循环才应该导致 TypeError 。 如果遇到无限多个不同的罐头,则永远递归是正确的行为。

(3)总结

2.对照PromiseA+规范手写 Promise

(1)Promise 状态

1.pending:待定

2.fulFilled:已解决

3.rejected:拒绝

        // 1.首先 Promise 有三个状态,初始状态为pending
        // 2.当 Promise 为 fulFilled 或 rejected状态时,不能转变成其它状态
        // 3.当 Promise 为 fulFilled 或 rejected状态时,
        // 必须有一个值(fulFilled是value,rejected是reason)
        // 4.Promise 接收一个回调函数
        function MyPromise(exector) {
            const PENDING = 'pending';
            const FULFILLED = 'fulFilled';
            const REJECTED = 'rejected';
            const that = this;
            that.status = PENDING;

            function resolve(value) {
                // 这里判断的原因就是Promise状态改为FULFILLED,不能再进行改变
                if (that.status === PENDING) {
                    that.status = FULFILLED;
                    that.value = value;
                }
            }

            function reject(reason) {
                // 这里判断的原因跟上面一样
                if (that.status === PENDING) {
                    that.status = REJECTED;
                    that.value = reason;
                }
            }
            try {
                exector(resolve, reject);
            } catch (error) {
                reject(error);
            }
        }

上述resolve和reject方法中如果不进行判断,下面这种情况就会再次改变promise的状态:

        let p = new MyPromise((resolve, rejrect) => {
            reject('拒绝');
            resolve('解决');
        })
        console.log(p);

上面这个例子,如果不进行判断,最终状态会是 fulfilled ,进行判断后状态是 rejected ,不会再被更改,这样才是满足Promise/A+规范的。

        class MyPromise {
            static PENDING = 'pending';
            static FULFILLED = 'fulFilled';
            static REJECTED = 'rejected';
            constructor(executor) {
                this.status = MyPromise.PENDING;
                this.value = null;
                try {
                    // 这个地方this.resolve只是拿到resolve方法,并不是this调用的
                    // 事实上是全局对象调用的,但是class里面默认是严格模式
                    // 如果不改变resolve的内部this指向,他的this是undefined
                    executor(this.resolve.bind(this), this.reject.bind(this));
                } catch (error) {
                    this.reject(error)
                }
            }
            resolve(value) {
                if (this.status === MyPromise.PENDING) {
                    this.status = MyPromise.FULFILLED;
                    this.value = value;
                }
            }
            reject(reason) {
                if (this.status === MyPromise.PENDING) {
                    this.status = MyPromise.REJECTED;
                    this.reason = reason;
                }
            }
        }
        // 测试
        let p = new MyPromise(function (resolve, reject) {
            reject('XKHM')
        });
        console.log(p);

2.then方法

promise.then(onFulfilled, onRejected)

1.then方法的两个参数onFulfilled和onRejected都是可选参数,如果这两个不是函数,将被忽略。

2.onFulfilled一定是在Promise执行完状态为fulFilled时调用,并接受一个参数value,并且最多被调用一次

3.onRejected一定是在Promise执行完状态为rejected时调用,并接受一个参数reason,并且最多被调用一次

4.onFulfilled和onRejected需要在下一轮事件循环中调用(异步调用)

5.onFulfilled和onRejected需要作为函数调用(没有自己的this值)

6.then方法可以被Promise链式调用

7.then方法必须返回一个Promise对象

提醒:

(1)then的时候需要处理Promise的三种状态,包括pending状态

(2)如果then本身在没经过处理的情况下就返回的是Promise,需要单独处理

3.Promise解决过程

Promise解决程序是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x)

1.如果Promise和x指向同一对象,以 TypeError 为据因拒绝执行 promise

2.如果 x 为 promise ,接受其状态

3.抑或 x 为对象或者函数


        class MyPromise {
            static PENDING = 'pending';
            static FULFILLED = 'fulFilled';
            static REJECTED = 'rejected';
            constructor(executor) {
                this.status = MyPromise.PENDING;
                this.value = null;
                this.callback = [];
                try {
                    // 这个地方this.resolve只是拿到resolve方法,并不是this调用的
                    // 事实上是全局对象调用的,但是class里面默认是严格模式
                    // 如果不改变resolve的内部this指向,他的this是undefined
                    executor(this.resolve.bind(this), this.reject.bind(this));
                } catch (error) {
                    this.reject(error)
                }
            }
            resolve(value) {
                if (this.status === MyPromise.PENDING) {
                    this.status = MyPromise.FULFILLED;
                    this.value = value;
                    setTimeout(_ => {
                        this.callback.forEach(callFn => {
                            callFn.onFulfilled(value)
                        });
                    })
                }
            }
            reject(value) {
                if (this.status === MyPromise.PENDING) {
                    this.status = MyPromise.REJECTED;
                    this.value = value;
                    setTimeout(_ => {
                        this.callback.forEach(callFn => {
                            callFn.onRejected(value)
                        });
                    })
                }
            }
            then(onFulfilled, onRejected) {
                if (typeof onFulfilled !== 'function') {
                    onFulfilled = value => value;
                }
                if (typeof onRejected !== 'function') {
                    onRejected = value => value;
                }
                let promise = new MyPromise((resolve, reject) => {
                    if (this.status === MyPromise.PENDING) {
                        this.callback.push({
                            onFulfilled: value => {
                                this.parse(promise, onFulfilled(this.value), resolve, reject)
                            },
                            onRejected: value => {
                                this.parse(promise, onRejected(this.value), resolve, reject)
                            }
                        })
                    }
                    if (this.status === MyPromise.FULFILLED) {
                        setTimeout(() => {
                            this.parse(promise, onFulfilled(this.value), resolve, reject)
                        })
                    }
                    if (this.status === MyPromise.REJECTED) {
                        setTimeout(() => {
                            this.parse(promise, onRejected(this.value), resolve, reject)
                        })
                    }
                });
                return promise;
            }
            parse(promise, res, resolve, reject) {
                if (promise == res) {
                    throw new TypeError('then返回的promise不能是跟then是相同的Promise');
                }
                try {
                    if (res instanceof MyPromise) {
                        res.then(resolve, reject)
                    } else {
                        resolve(res)
                    }
                } catch (error) {
                    reject(error)
                }
            }
        }
        class MyPromise {
            static PENDING = 'pending';
            static FULFILLED = 'fulFilled';
            static REJECTED = 'rejected';
            constructor(executor) {
                this.status = MyPromise.PENDING;
                this.value = null;
                this.callback = [];
                try {
                    // 这个地方this.resolve只是拿到resolve方法,并不是this调用的
                    // 事实上是全局对象调用的,但是class里面默认是严格模式
                    // 如果不改变resolve的内部this指向,他的this是undefined
                    executor(this.resolve.bind(this), this.reject.bind(this));
                } catch (error) {
                    this.reject(error)
                }
            }
            resolve(value) {
                if (this.status === MyPromise.PENDING) {
                    this.status = MyPromise.FULFILLED;
                    this.value = value;
                    setTimeout(_ => {
                        this.callback.forEach(callFn => {
                            callFn.onFulfilled(value)
                        });
                    })
                }
            }
            reject(value) {
                if (this.status === MyPromise.PENDING) {
                    this.status = MyPromise.REJECTED;
                    this.value = value;
                    setTimeout(_ => {
                        this.callback.forEach(callFn => {
                            callFn.onRejected(value)
                        });
                    })
                }
            }
            then(onFulfilled, onRejected) {
                if (typeof onFulfilled !== 'function') {
                    onFulfilled = value => value;
                }
                if (typeof onRejected !== 'function') {
                    onRejected = value => value;
                }
                let promise = new MyPromise((resolve, reject) => {
                    if (this.status === MyPromise.PENDING) {
                        this.callback.push({
                            onFulfilled: value => {
                                this.parse(promise, onFulfilled(this.value), resolve, reject)
                            },
                            onRejected: value => {
                                this.parse(promise, onRejected(this.value), resolve, reject)
                            }
                        })
                    }
                    if (this.status === MyPromise.FULFILLED) {
                        setTimeout(() => {
                            this.parse(promise, onFulfilled(this.value), resolve, reject)
                        })
                    }
                    if (this.status === MyPromise.REJECTED) {
                        setTimeout(() => {
                            this.parse(promise, onRejected(this.value), resolve, reject)
                        })
                    }
                });
                return promise;
            }
            parse(promise, res, resolve, reject) {
                if (promise == res) {
                    throw new TypeError('then返回的promise不能是跟then相同的Promise');
                }
                try {
                    if (res instanceof MyPromise) {
                        res.then(resolve, reject)
                    } else {
                        resolve(res)
                    }
                } catch (error) {
                    reject(error)
                }
            }
            static resolve(value) {
                return new MyPromise((resolve, reject) => {
                    if (value instanceof MyPromise) {
                        value.then(resolve, reject);
                    } else {
                        resolve(value);
                    }
                })
            }
            static reject(reason) {
                return new MyPromise((_, reject) => {
                    reject(reason);
                })
            }
            static all(promises) {
                let resolves = [];
                return new MyPromise((resolve, reject) => {
                    promises.forEach((promise, index) => {
                        promise.then(value => {
                            resolves.push(value)
                            if (resolves.length === promises.length) {
                                resolve(resolves);
                            }
                        }, reason => {
                            reject(reason);
                        })
                    })
                })
            }
            static race(promises) {
                return new MyPromise((resolve, reject) => {
                    promises.forEach(promise => {
                        promise.then(value => {
                            resolve(value)
                        })
                    })
                })
            }
        } 

4.测试

使用 Promise/A+ 测试工具

npm install promises-aplus-tests -g
npx promises-aplus-tests promise.js

5.提醒

(1)Promise内部错误被吞掉了,那是被谁吞掉的呢,是被reject吞掉的

const pending = 'pending';
const fulFilled = 'fulFilled';
const rejected = 'rejected';
const resolveFn = [];
const rejectFn = [];

// Promise/A+规范

// 1. Promise状态

// (1) 当Promise为pending状态时,可以被转换成另外两种状态
// (2) 当Promise为fulFilled状态时,不能被更改,且必须有一个不能被更改的value值
// (3) 当Promise为rejected状态时,不能被更改,且必须有一个不能被更改的reason值

function MyPromise(execute){
    const that = this;
    that.status = pending;

    function resolve(value){
        if(that.status === pending){
            that.status = fulFilled;
            that.value = value;
        }
    }

    function reject(reason){
        if(that.status === pending){
            that.status = rejected;
            that.value = reason;
        }
    }

    // 如果执行中有错误,会立马执行reject
    // 也就是说Promise中的错误是被reject吞掉的
    // try...catch是捕获不到的
    try {
        execute(resolve,reject);   
    } catch (error) {
        reject(error)
    }
}

// 2.then方法

// 首先,then方法是挂在Promise原型上的一个方法

// (1)then方法接收两个回调函数,如果不是函数忽略
// (2)then方法返回一个Promise
// (3)then方法需要异步执行
// (4)then可以被链式调用
// (5)如果onResolve是函数,必须在Promise状态为fulFilled后调用
// (6)如果onRejected是函数,必须在Promise状态为rejected后调用
// (7)then没有自己的this(也可以理解为then方法是挂在Promise原型上的)
MyPromise.prototype.then = function(onResolve,onRejected){
    if(typeof onResolve !== 'function'){
        return onResolve = value => value
    }
    if(typeof onRejected !== 'function'){
        return onRejected = reason => reason
    }
    let that = this;
    let p = new MyPromise((resolve,reject) => {
        if(that.status === fulFilled){
            setTimeout(_ => {
                onResolve(that.value)
            })
        }
        if(that.status === rejected){
            setTimeout(_ => {
                onRejected(that.value)
            })
        }
    })
    return p;
}


// 测试
try {
    let p = new MyPromise((resolve,reject) => {
        throw new TypeError('抛出错误')
    })
    console.log(p); 
} catch (error) {
    console.log(error);
}
// p.then();

try {
    let p1 = new Promise(resolve => {
        throw new TypeError('抛出错误')
    })
    console.log(p1);
} catch (error) {
    console.log(error);
}

Promise实例调用then时,如果then回调函数里面抛出了错误,也会被reject吞掉

6.手写Promise的意义

相当于照着一个规范然后用代码区实现,相当于深入了解源码,只是Promise源码才100多行,算阅读一个小型项目的源码把。

const pending = 'pending';
const fulFilled = 'fulFilled';
const rejected = 'rejected';

// Promise/A+规范

// 1. Promise状态

// (1) 当Promise为pending状态时,可以被转换成另外两种状态
// (2) 当Promise为fulFilled状态时,不能被更改,且必须有一个不能被更改的value值
// (3) 当Promise为rejected状态时,不能被更改,且必须有一个不能被更改的reason值

function MyPromise(execute){
    const that = this;
    that.status = pending;
    that.resolveFn = [];
    that.rejectFn = [];
    function resolve(value){
        setTimeout(_ => {
            if(that.status === pending){
                that.status = fulFilled;
                that.value = value;
                that.resolveFn.forEach(f => {
                    f(that.value)
                })
            }
        })
    }

    function reject(reason){
        setTimeout(_ => {
            if(that.status === pending){
                that.status = rejected;
                that.value = reason;
                that.rejectFn.forEach(f => {
                    f(that.value)
                })
            }
        })
    }

    // 如果执行中有错误,会立马执行reject
    // 也就是说Promise中的错误是被reject吞掉的
    // try...catch是捕获不到的
    try {
        execute(resolve,reject);   
    } catch (error) {
        reject(error)
    }
}

// 2.then方法

// 首先,then方法是挂在Promise原型上的一个方法

// (1)then方法接收两个回调函数,如果不是函数忽略
// (2)then方法返回一个Promise
// (3)then方法需要异步执行
// (4)then可以被链式调用
// (5)如果onResolve是函数,必须在Promise状态为fulFilled后调用
// (6)如果onRejected是函数,必须在Promise状态为rejected后调用
// (7)then没有自己的this(也可以理解为then方法是挂在Promise原型上的)
MyPromise.prototype.then = function(onResolve,onRejected){
    if(typeof onResolve !== 'function'){
        onResolve = value => value
    }
    if(typeof onRejected !== 'function'){
        onRejected = reason => {
            throw reason
        }
    }
    const that = this;
    let promise;
    if(that.status === fulFilled){
        promise = new MyPromise((resolve,reject) => {
            setTimeout(_ => {
                try {
                    let x = onResolve(that.value);
                    resolvePromise(promise,x,resolve,reject)
                } catch (error) {
                    reject(error)
                }
            })
        })
    }

    if(that.status === rejected){
        promise = new MyPromise((resolve,reject) => {
            setTimeout(_ => {
                try {
                    let x = onRejected(that.value);
                    resolvePromise(promise,x,resolve,reject)
                } catch (error) {
                    reject(error)
                }   
            })
        })
    }

    if(that.status === pending){
        promise = new MyPromise((resolve,reject) => {
            that.resolveFn.push(_ => {
                try {
                    let x = onResolve(that.value);
                    resolvePromise(promise,x,resolve,reject)
                } catch (error) {
                    reject(error)
                }
            })

            that.rejectFn.push(_ => {
                try {
                    let x = onRejected(that.value);
                    resolvePromise(promise,x,resolve,reject)
                } catch (error) {
                    reject(error)
                }   
            })
        })
    }
    return promise;
}

// 3.Promise执行程序

// 1.promise和x指向同一个对象,执行reject()
// 2.x是promise,采用它的状态
// 3.x是对象或函数,处理
// 4.then不是函数
function resolvePromise(promise,x,resolve,reject){
    if(promise === x){
        return reject(new TypeError('promise不能和x相等'))
    }
    if(x instanceof MyPromise){
        if(x.status === fulFilled){
            resolve(x.value)
        }else if(x.status === rejected){
            reject(x.value)
        }else{
            x.then(y => {
                resolvePromise(promise,y,resolve,reject)
            },reject)
        }
    }else if(x !== null && (typeof x === 'object' || typeof x === 'function')){
        let executed;
        try {
            let then = x.then;
            if(typeof then === 'function'){
                then.call(x,y => {
                    if(executed){
                        return false
                    }
                    executed = true;
                    resolvePromise(promise,y,resolve,reject)
                },e => {
                    if(executed){
                        return false
                    }
                    executed = true;
                    reject(e);
                })
            }else{
                resolve(x)
            }
        } catch (error) {
            if(executed){
                return false
            }
            executed = true;
            reject(error);
        }
    }else{
        resolve(x)
    }
}