我手写了一个Promise

414 阅读6分钟

基于ES6新特性——class关键字,我手写了一个简单的promise对象,目前已实现构造器函数,then方法,catch方法,resolve、reject、all、race静态方法

1、Promise简介

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

2、MyPromise的总体结构

我手写的MyPromise采用class关键字声明类,定义了PENDINGRESOLVED REJECTED三个常量,用于表示MyPromise实例的三种状态,类内定义了构造器函数、实例方法和静态方法,最后通过CommonJS模块化规范向外暴露。

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
class MyPromise {
  static resolve = (data) => {};
  static reject = (data) => {};
  static all = (promises) => {};
  static race = (promises) => {};

  constructor(excutor) {
    //执行器函数
    this.excutor = excutor;
    //状态
    this.status = PENDING;
    //数据
    this.data = undefined;
    //回调队列
    this.callbacks = [];
  }
  then = (onResolved, onRejected) => {};
  catch = (onRejected) => {};
}
module.exports = MyPromise;

3、MyPromise的构造器

构造器constructor是MyPromise的核心部分,其中声明了执行器函数、状态、值和回调队列四个属性。

constructor(excutor) {
    //执行器函数
    this.excutor = excutor;
    //状态
    this.status = PENDING;
    //值
    this.data = undefined;
    //回调队列
    this.callbacks = [];

    this.resolve = (data) => {
      if (this.status !== PENDING) {
        //如果不是挂起状态, 则直接返回
        return;
      } else {
        this.status = RESOLVED;
        this.data = data;
        //执行回调队列中的回调函数
        if (this.callbacks.length > 0) {
          this.callbacks.forEach((callbackObj) => {
            setTimeout(() => {
              callbackObj.onResolved();
            });
          });
        }
      }
    };

    this.reject = (data) => {
      if (this.status !== PENDING) {
        //如果不是挂起状态, 则直接返回
        return;
      } else {
        this.status = REJECTED;
        this.data = data;
        if (this.callbacks.length > 0) {
          this.callbacks.forEach((callbackObj) => {
            setTimeout(() => {
              callbackObj.onRejected();
            });
          });
        }
      }
    };
    try {
      excutor(this.resolve, this.reject);
    } catch (error) {
      reject(error);
    }
  }

执行器函数excutor内部有两个参数, 用于确定MyPromise实例的状态和内部的值,我声明为this.resolvethis.reject。以this.resolve为例,调用时先判断此时MyPromise实例的状态,如果不是pending,则直接返回;否则赋予MyPromise成功的状态resolved与相应的this.data,在之后并判断回调队列中是否有待执行的任务,如果有,则遍历并全部执行。关于回调队列会在then方法中解释。

4、实例方法then

MyPromise 实例具有then方法,它的作用是为 MyPromise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。 then方法内部稍微有些复杂,我将分块进行介绍。

then = (onResolved, onRejected) => {
    onResolved =
      typeof onResolved === "function" ? onResolved : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (error) => {
            throw error;
          };
    return new MyPromise((resolve, reject) => {
      const handle = (callback) => {
        setTimeout(() => {
          try {
            const result = callback(this.data);
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (error) {
            reject(error);
          }
        });
      };

      if (this.status === RESOLVED) {
        handle(onResolved);
      } else if (this.status === REJECTED) {
        handle(onRejected);
      } else {
        //如果promise仍属于挂起状态, 则加入回调队列
        this.callbacks.push({
          onResolved() {
            handle(onResolved);
          },
          onRejected() {
            handle(onRejected);
          },
        });
      }
    });
  };
onResolved = 
    typeof onResolved === "`function`" ? onResolved : (value) => value;
onRejected = 
    typeof onRejected === "function" 
    ? onRejected 
    : (error) => { throw error; };

onResolvedonRejectedthen方法的参数,分别表示成功和失败的回调,此处对onResolvedonRejected进行类型判断,主要是为了实现MyPromise的穿透功能,如果使用者已经在then方法中传入函数(即typeof onResolved/ onRejected=== "function"),则不进行任何处理,否则赋予(value) => value(error) => { throw error; },将状态与值向后传递,在之后调用then方法进行处理。

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

我们知道ES6新特性中Promise的then方法会返回一个新的Promise实例,因此我们通过return new MyPromise((resolve, reject) => {});包裹内部的核心代码,保证then方法能够返回一个MyPromise实例。

if (this.status === RESOLVED) {
    handle(onResolved);
} else if (this.status === REJECTED) {
    handle(onRejected);
} else {
    //如果promise仍属于挂起状态, 则加入回调队列
    this.callbacks.push({
        onResolved() {
            handle(onResolved);
        },
        onRejected() {
            handle(onRejected);
        },
    });
}

这里对this.status进行判断,不同的状态执行不同的代码块, handle是对于成功和失败状态共同代码部分的封装,稍微进行分析; 当MyPromise实例处于挂起状态时,则加入回调队列,等待MyPromise实例状态改变后再执行。

const handle = (callback) => {
    setTimeout(() => {
        try {
            const result = callback(this.data);
            if (result instanceof MyPromise) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
         } catch (error) {
            reject(error);
         }
 });

handle函数是对成功onResolved和失败onRejected回调中相同代码的封装,它的作用主要是执行onResolvedonRejected回调,并返回一个新的MyPromise实例,以实现其链式调用功能,它通过setTimeout模拟异步效果。result instanceof MyPromise是对onResolvedonRejected回调返回的结果进行判断,如果是MyPromise,它就是回调的返回结果,否则调用resolve(result),将结果封装到MyPromise的内部返回。为了保证代码的严谨性,我在最外部还用try catch进行包裹,如果有错误代码,则捕获并返回一个失败的MyPromise实例

5、catch方法

catch = (onRejected) => {
   this.then(()=>{}, onRejected);
}

catch实际上就是个语法糖myPromise.catch(onRejected) 就相当于 myPromise.then(() => {}, onRejected)

6、resolve静态方法和reject静态方法

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

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

MyPromise.resolve(data)MyPromise.reject(data),同样是语法糖,MyPromise.resolve(data)就相当于new MyPromise((resolve, reject) => {resolve(data);})对于ES6新特性中Promise API了解的朋友应该很容易写出

7、all静态方法

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

static all = (promises) => {
    let resolvedCount = 0;
    let values = new Array(promises.length);
    return new MyPromise((resolve, reject) => {
      //遍历获取每个promise的结果
      promises.forEach((p, index) => {
        p.then(
          (value) => {
            values[index] = value;
            resolvedCount++;
            if (resolvedCount === promises.length) {
              resolve(values);
            }
          },
          (reason) => {
            reject(reason);
          }
        );
      });
    });
  };

在我的代码中,定义values变量用于保存成功的值,变量resolvedCount用于对成功的值进行计数,之后遍历数组promises,如果失败, 直接通过reject(reason)使all方法返回一个失败的MyPromise实例, 如果成功,则将值存到values数组中相应的位置中(注意:这里成功的值在values数组中的位置取决于promises数组中对应实例的位置,而不是完成的先后顺序),然后使resolvedCount加1,判断resolvedCount的值,如果与promises长度相等,则说明所有的promises数组中所有的MyPromises实例都已经返回了成功的值。

8、race静态方法

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

static race = (promises) => {
    return new MyPromise((resolve, reject) => {
      promises.forEach((p) => {
        p.then(
          (value) => {
            resolve(value);
          },
          (reason) => {
            reject(reason);
          }
        );
      });
   });
 };

race的实现与all类似,都是对promises数组进行遍历取值。