Promise 分析与实现(手撕&通过A+规范case)

Promise 分析与实现

一、参考资料

二、术语

  • promise是一个有 then 方法的对象或者函数
  • thenable是一个有 then 方法的对象或者函数
  • value是 promise 状态成功时的值,也就是 resolve 的参数, 包括各种数据类型, 也包括 undefined/thenable 或者是 promise
  • reason 是 promise 状态失败时的值, 也就是 reject 的参数, 表示拒绝的原因
  • exception是一个使用 throw 抛出的异常值

三、Promise States

promise 应该有三种状态. 要注意他们之间的流转关系.

1.pending

1.1 初始的状态, 可改变.
1.2 一个 promise 在 resolve 或者 reject 前都处于这个状态。
1.3 可以通过 resolve -> fulfilled 状态;
1.4 可以通过 reject -> rejected 状态;

2.fulfilled

2.1 最终态, 不可变.
2.2 一个 promise 被 resolve 后会变成这个状态.
2.3 必须拥有一个 value 值

3.rejected

3.1 最终态, 不可变.
3.2 一个 promise 被 reject 后会变成这个状态
3.3 必须拥有一个 reason

Tips: 总结一下, 就是 promise 的状态流转是这样的

pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected

四、Then 方法

promise 应该提供一个 then 方法, 用来访问最终的结果, 无论是 value 还是 reason.

promise.then(onFulfilled, onRejected);

1.参数要求

1.1 onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略.
1.2 onRejected 必须是函数类型, 如果不是函数, 应该被忽略.

2.onFulfilled 特性

2.1 在 promise 变成 fulfilled 时,应该调用 onFulfilled, 参数是 value
2.2 在 promise 变成 fulfilled 之前, 不应该被调用.
2.3 只能被调用一次 (所以在实现的时候需要一个变量来限制执行次数)

3.onRejected 特性

3.1 在 promise 变成 rejected 时,应该调用 onRejected, 参数是 reason
3.2 在 promise 变成 rejected 之前, 不应该被调用.
3.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)

4.onFulfilled 和 onRejected 应该是微任务

这里用queueMicrotask来实现微任务的调用.

5.then 方法可以被调用多次

5.1 promise 状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onFulfilled 的回调)
5.2 promise 状态变成 rejected 后,所有的 onRejected 回调都需要按照then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onRejected 的回调)

6.返回值

  • then 应该返回一个 promise
promise2 = promise1.then(onFulfilled, onRejected);

6.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolvePromise
6.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 e, promise2 需要被 reject
6.3 如果 onFulfilled 不是一个函数, promise2 以 promise1 的 value 触发 fulfilled
6.4 如果 onRejected 不是一个函数, promise2 以 promise1 的 reason 触发 rejected

7.resolvePromise

resolvePromise(promise2, x, resolve, reject);
  • 如果 promise2 和 x 相等,那么reject TypeError,防止循环引用,造成死循环
  • 如果 x 是一个 promise
    • 如果 x 是 pending 态,那么 promise 必须要在 pending,直到 x 变成 fulfilled or rejected.
    • 如果 x 被 fulfilled, 用相同的 value 执行 promise
    • 如果 x 被 rejected, 用相同的 reason 拒绝 promise
  • 如果 x是一个 object 或者 是一个 function
    • 首先取出 then,let then = x.then.
    • 如果x.then 这步出错,那么 reject(e)
    • 如果then 是一个函数,then.call(x, resolvePromiseFn, rejectPromiseFn)
    • resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);
    • rejectPromiseFn 的 入参是 r, resolvePromise(promise2, r, resolve, reject)
    • 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。使用 called 标识
    • 如果调用 then 抛出异常 e
    • 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
  • 如果 then 不是一个 function. resolve(x)

五、实现 promise

1.定义并设置三种状态,传入执行器 exec

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(exec) {
    this.status = PENDING;
    this.value = null;
    this.reason = null;

    // 执行器传入后立即执行
    exec();
  }
}

2.为 exec 添加 reject 和 resolve 方法,执行回调

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(exec) {
    this.status = PENDING;
    this.value = null;
    this.reason = null;

    // resolve和reject被回调的时候,状态流转
    // 这里使用箭头函数,绑定resolve和reject的this始终指向MyPromise
    let resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    };

    let reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    };
    // 若执行器报错,直接reject出去
    try {
      // 执行器传入后立即执行
      exec(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}

3.简单实现 then 方法

then(onFulfilled, onRejected) {
	if (this.status === FULFILLED) {
    // 调用成功回调,并且把值返回
    onFulfilled(this.value);
  } else if (this.status === REJECTED) {
    // 调用失败回调,并且把原因返回
    onRejected(this.reason);
  }
}

到这个地方一个简单的 promise 的骨架已经形成了,写一个测试检查一下

const promise = new MyPromise((resolve, reject) => {
  resolve('success');
  reject('err');
  // 如果这里使用异步的方法,上述的then是感觉不到的
  // setTimeout(() => {
  //     resolve('success')
  // })
});

promise.then(
  (value) => {
    console.log('resolve', value);
  },
  (reason) => {
    console.log('reject', reason);
  },
);

// 输出: resolve success

4.添加 then 方法的对异步的处理

思考:为什么使用异步的方法就感受不到了呢?
上面的写法, 是在 then 函数被调用的瞬间就会执行.
那这时候如果 status 还没变成 fulfilled 或者 rejected 怎么办, 很有可能还是 pending 的
那么我们首先要拿到所有的回调, 然后才能在某个时机去执行他.
新建两个数组, 来分别存储成功和失败的回调, 调用 then 的时候, 如果还是 pending 就存入数组

class MyPromise {
  constructor(exec) {
    this.status = PENDING;
    this.value = null;
    this.reason = null;

    this.resolveCallbacks = [];
    this.rejectCallbacks = [];

    // resolve和reject被回调的时候,状态流转
    // 这里使用箭头函数,绑定resolve和reject的this始终指向MyPromise
    let resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.resolveCallbacks.forEach((fn) => fn());
      }
    };

    let reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.rejectCallbacks.forEach((fn) => fn());
      }
    };

    // 若执行器报错,直接reject出去
    try {
      // 执行器传入后立即执行
      exec(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      // 调用成功回调,并且把值返回
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    } else if (this.status === PENDING) {
      this.resolveCallbacks.push(onFulfilled);
      this.rejectCallbacks.push(onRejected);
    }
  }
}

5.完善 then 方法-(链式调用)

  1. onFulfilled 和 onRejected 如果不是函数,就返回原 value 或 reason
  2. then 方法返回一个 promise 对象
    1. onFulfilled 或 onRejected 执行的结果是 x,调用 resolvePromise
    2. 如果 onFulfilled 或者 onRejected 执行时抛出异常 e,promise2 需要被 reject,其 reason 为 e
  3. onFulfilled 和 onRejected 是微任务,需要使用 queueMicrotask 或者 setTimeout 包裹
isFunction(params) {
    return typeof params === 'function';
}

then(onFulfilled, onRejected) {
    // 1.onFulfilled和onRejected如果不是函数,就返回原value或reason
    const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : value => value;
    const rejectedFn = this.isFunction(onRejected) ? onRejected : reason => { throw reason };

    // 2.then方法返回一个promise对象
    const p2 = new MyPromise((resolve, reject) => {
        if (this.status === FULFILLED) {
            // fulFilledFn(this.value)
            // 3.3. onFulfilled和onRejected是微任务,需要使用queueMicrotask或者setTimeout包裹
            queueMicrotask(() => {
              // 2.2 如果onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject,其reason为e
              try {
                // 2.1 onFulfilled或onRejected执行的结果是x,调用resolvePromise
                // 获取成功回调函数的执行结果
                const x = fulFilledFn(this.value);
                // 传入 resolvePromise 集中处理
                this.resolvePromise(p2, x, resolve, reject);
              } catch (e) {
                  reject(e);
              }
            })
        } else if (this.status === REJECTED) {
            // rejectedFn(this.reason);
            queueMicrotask(() => {
              try {
                const x = rejectedFn(this.value);
                // 传入 resolvePromise 集中处理
                this.resolvePromise(x, resolve, reject);
              } catch (e) {
                  reject(e);
              }
            })
        } else if (this.status === PENDING) {
            this.resolveCallbacks.push(() => {
                queueMicrotask(() => {
                  try {
                    const x = fulFilledFn(this.value);
                    // 传入 resolvePromise 集中处理
                    this.resolvePromise(x, resolve, reject);
                  } catch (e) {
                    reject(e);
                  }
                })
            });
            this.rejectCallbacks.push(() => {
              queueMicrotask(() => {
                try {
                  const x = rejectedFn(this.value);
                  // 传入 resolvePromise 集中处理
                  this.resolvePromise(x, resolve, reject);
                } catch (e) {
                  reject(e);
                }
              })
            });
        }
    });
    return p2;
}

resolvePromise(x, resolve, reject) {
    // 判断x是不是 MyPromise 实例对象
    if (x instanceof MyPromise) {
        // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
        // x.then(value => resolve(value), reason => reject(reason))
        // 简化之后
        x.then(resolve, reject);
    } else {
        // 普通值
        resolve(x);
    }
}

发现上面 promise2 中很多重复的代码,进行抽离

then(onFulfilled, onRejected) {
    // 1.onFulfilled和onRejected如果不是函数,就返回原value或reason
    const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : value => value;
    const rejectedFn = this.isFunction(onRejected) ? onRejected : reason => { throw reason };

    // 2.then方法返回一个promise对象
    const p2 = new MyPromise((resolve, reject) => {

        const fulfilledMicrotask = () => {
            // 3.3. onFulfilled和onRejected是微任务,需要使用queueMicrotask或者setTimeout包裹
            queueMicrotask(() => {
                // 2.2 如果onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject,其reason为e
                try {
                    // 2.1 onFulfilled或onRejected执行的结果是x,调用resolvePromise
                    // 获取成功回调函数的执行结果
                    const x = fulFilledFn(this.value);
                    // 传入 resolvePromise 集中处理
                    this.resolvePromise(p2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        }

        const rejectedMicrotask = () => {
            queueMicrotask(() => {
                try {
                    const x = rejectedFn(this.reason);
                    // 传入 resolvePromise 集中处理
                    this.resolvePromise(p2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        }

        if (this.status === FULFILLED) {
            // fulFilledFn(this.value)
            fulfilledMicrotask();
        } else if (this.status === REJECTED) {
            // rejectedFn(this.reason);
            rejectedMicrotask();
        } else if (this.status === PENDING) {
            this.resolveCallbacks.push(fulfilledMicrotask);
            this.rejectCallbacks.push(rejectedMicrotask);
        }
    });

    return p2;
}

6.完善 resolvePromise 的处理

其实整体的处理逻辑与原来相似,不过是对边界情况更加细化了

resolvePromise(x, resolve, reject) {
    // 判断x是不是 MyPromise 实例对象
    if (x instanceof MyPromise) {
        // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
        // x.then(value => resolve(value), reason => reject(reason))
        // 简化之后
        x.then(resolve, reject);
    } else {
        // 普通值
        resolve(x);
    }
}

resolvePromise(promise2, x, resolve, reject);
  • 防止循环引用,这里要给 resolvePromise 函数签名加一个新参数,判断 promise2 和 x 是否为同一个 promise
  • 如果 x是一个 object 或者 是一个 function
    • 如果 x 是 null,直接 resolve(x)出去
    • 然后取出 then,let then = x.then.
    • 如果x.then 这步出错,那么 reject(e)
    • 如果then 是一个函数,then.call(x, resolvePromiseFn, rejectPromiseFn)
      • resolvePromiseFn 的入参是 y, 如果被调用,执行resolvePromise(promise2, y, resolve, reject);
      • rejectPromiseFn 的入参是 r, 如果被调用,执行 reject®
      • 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。使用called标识
      • 如果调用 then 抛出异常 e
      • 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略,if(called) return
    • 如果 then 不是一个 function. resolve(x)
resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        throw new TypeError('循环引用')
    }

    if (typeof x === 'object' || typeof x === 'function') {
        if (x === null) {
            return resolve(x);
        }
        let then;

        try {
            then = x.then;
        } catch (e) {
            return reject(e);
        }
        if (typeof then === 'function') {
            let called = false;
            try {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    this.resolvePromise(promise2, y, resolve, reject)
                }, r => {
                    if (called) return;
                    called = true;
                    reject(r);
                })
            } catch (e) {
                if (called) return;
                reject(e);
            }
        } else {
            resolve(x);
        }
    } else {
        resolve(x);
    }
}

7.添加 resolve、reject、catch 静态方法

static resolve(params) {
      if (params instanceof MyPromise) {
          return params;
      }
      return new MyPromise((resolve, reject) => {
          resolve(params);
      })
  }

static reject(reason) {
    return new MyPromise((resolve, reject) => {
        reject(reason);
    })
}

catch(onRejected){
    return this.then(null, onRejected)
}

8.实现 race、all、allSettled 方法

const p = Promise.race([p1, p2, p3]);
该方法是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。
那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

static race(promises) {
    if (!Array.isArray(promises)) {
        throw new TypeError('arguments must be an array')
    }
    return new MyPromise((resolve, reject) => {
        if (promises.length === 0) {
            return resolve();
        }
        for (let i = 0; i < promises.length; i++) {
            MyPromise.resolve(promises[i]).then(resolve, reject);
        }
    })
}

static all(promises) {
    return new MyPromise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            throw new TypeError('arguments must be an array')
        }
        if (promises.length === 0) {
            return resolve([]);
        }
        let result = [];
        let count = 0;
        for (let i = 0; i < promises.length; i++) {
            MyPromise.resolve(promises[i]).then(value => {
                result[i] = value;
                count++;
                if (count === promises.length) {
                    resolve(result);
                }
            }, reject);
        }
    })
}

// 返回所有promise的状态和结果
static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            throw new TypeError('arguments must be an array')
        }
        let count = 0;
        let result = [];

        for (let i = 0; i < promises.length; i++) {
            MyPromise.resolve(promises[i]).then(value => {
                result[i] = {
                    status: 'fulfilled',
                    value
                }
                count++;
                if (count === promises.length) {
                    resolve(result);
                }
            }, reason => {
                result[i] = {
                    status: 'rejected',
                    reason
                }
                count++;
                if (count === promises.length) {
                    resolve(result);
                }
            })
        }
    })
}

六、使用 promises-aplus-tests 进行测试

// npm i promises-aplus-tests -g
// 命令行执行 => promises-aplus-tests [待测试文件] 即可验证
MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
};
module.exports = MyPromise;

七、完整代码

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(exec) {
        this.status = PENDING;
        this.value = null;
        this.reason = null;

        this.resolveCallbacks = [];
        this.rejectCallbacks = [];

        // resolve和reject被回调的时候,状态流转
        // 这里使用箭头函数,绑定resolve和reject的this始终指向MyPromise
        let resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.resolveCallbacks.forEach(fn => fn())
            }
        }

        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.rejectCallbacks.forEach(fn => fn())
            }
        }
        // 若执行器报错,直接reject出去
        try {
            // 执行器传入后立即执行
            exec(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    isFunction(params) {
        return typeof params === 'function';
    }

    then(onFulfilled, onRejected) {
        // 1.onFulfilled和onRejected如果不是函数,就返回原value或reason
        const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : value => value;
        const rejectedFn = this.isFunction(onRejected) ? onRejected : reason => { throw reason };

        // 2.then方法返回一个promise对象
        const p2 = new MyPromise((resolve, reject) => {

            const fulfilledMicrotask = () => {
                // 3.3. onFulfilled和onRejected是微任务,需要使用queueMicrotask或者setTimeout包裹
                queueMicrotask(() => {
                    // 2.2 如果onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject,其reason为e
                    try {
                        // 2.1 onFulfilled或onRejected执行的结果是x,调用resolvePromise
                        // 获取成功回调函数的执行结果
                        const x = fulFilledFn(this.value);
                        // 传入 resolvePromise 集中处理
                        this.resolvePromise(p2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            }

            const rejectedMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        const x = rejectedFn(this.reason);
                        // 传入 resolvePromise 集中处理
                        this.resolvePromise(p2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            }

            if (this.status === FULFILLED) {
                // fulFilledFn(this.value)
                fulfilledMicrotask();
            } else if (this.status === REJECTED) {
                // rejectedFn(this.reason);
                rejectedMicrotask();
            } else if (this.status === PENDING) {
                this.resolveCallbacks.push(fulfilledMicrotask);
                this.rejectCallbacks.push(rejectedMicrotask);
            }
        });

        return p2;
    }

    resolvePromise(promise2, x, resolve, reject) {
        if (promise2 === x) {
            throw new TypeError('Chaining cycle ')
        }

        if (typeof x === 'object' || typeof x === 'function') {
            if (x === null) {
                return resolve(x);
            }
            let then;

            try {
                then = x.then;
            } catch (e) {
                return reject(e);
            }
            if (typeof then === 'function') {
                let called = false;
                try {
                    then.call(x, y => {
                        if (called) return;
                        called = true;
                        this.resolvePromise(promise2, y, resolve, reject)
                    }, r => {
                        if (called) return;
                        called = true;
                        reject(r);
                    })
                } catch (e) {
                    if (called) return;
                    reject(e);
                }
            } else {
                resolve(x);
            }
        } else {
            resolve(x);
        }
    }

    static resolve(params) {
        if (params instanceof MyPromise) {
            return params;
        }
        return new MyPromise((resolve, reject) => {
            resolve(params);
        })
    }

    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            reject(reason);
        })
    }

    catch(onRejected) {
        return this.then(null, onRejected)
    }

    static race(promises) {
        if (!Array.isArray(promises)) {
            throw new TypeError('arguments must be an array')
        }
        return new MyPromise((resolve, reject) => {
            if (promises.length === 0) {
                return resolve();
            }
            for (let i = 0; i < promises.length; i++) {
                MyPromise.resolve(promises[i]).then(resolve, reject);
            }
        })
    }

    static all(promises) {
        return new MyPromise((resolve, reject) => {
            if (!Array.isArray(promises)) {
                throw new TypeError('arguments must be an array')
            }
            if (promises.length === 0) {
                return resolve([]);
            }
            let result = [];
            let count = 0;
            for (let i = 0; i < promises.length; i++) {
                MyPromise.resolve(promises[i]).then(value => {
                    result[i] = value;
                    count++;
                    if (count === promises.length) {
                        resolve(result);
                    }
                }, reject);
            }
        })
    }

    static allSettled(promises) {
        return new MyPromise((resolve, reject) => {
            if (!Array.isArray(promises)) {
                throw new TypeError('arguments must be an array')
            }
            let count = 0;
            let result = [];

            for (let i = 0; i < promises.length; i++) {
                MyPromise.resolve(promises[i]).then(value => {
                    result[i] = {
                        status: 'fulfilled',
                        value
                    }
                    count++;
                    if (count === promises.length) {
                        resolve(result);
                    }
                }, reason => {
                    result[i] = {
                        status: 'rejected',
                        reason
                    }
                    count++;
                    if (count === promises.length) {
                        resolve(result);
                    }
                })
            }
        })
    }
}

往期精彩文章

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个爱编程的男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值