【JavaScript】其实真不难,带你一步一步手撕promise。

前言

promise是ES6新推出的一种优雅的异步任务的解决方法。相信大家对它是既熟悉又陌生,熟悉是因为我们在开发的时候经常都会用到它,陌生是因为当我们谈到手撕promise的时候却感到无从下手,今天就带大家如何一步一步地实现promise。

1、new Promise()

前置知识

  1. new Promise中传入一个函数,这个函数是立即执行的。
  2. 传入的函数有两个参数resolve和reject,执行后会改变promise状态。
  3. promise有三种状态pending、fulfilled、rejected。状态一旦发生改变,就不会再变。

我们先看下这三种状态下的promise都长什么样:

let p1 = new Promise((resolve, reject) => {});
let p2 = new Promise((resolve, reject) => {
  resolve("resolve");
});
let p3 = new Promise((resolve, reject) => {
  reject("reject");
});

console.log(p1);//pending
console.log(p2);//fulfilled
console.log(p3);//rejected

pending状态

fulfilled状态

rejected状态

 

从上面可以得到的信息有:

  • PromiseState代表当前Promise的状态。
  • PromiseResult代表当前Promise状态改变时传入的参数,默认为undefined。 

为了代码简洁,这里就使用class关键字来实现。

class MyPromise {
    //Promise三种状态
  static PENDING = "pending";
  static FULLFILLED = "fullfilled";
  static REJECTED = "rejected";
  constructor(fn) {
    this.PromiseState = MyPromise.PENDING;//    当前promise的状态
    this.PromiseResult = undefined;//    promise收到的参数
    fn();//    传入的函数立即执行,这里还未传入resolve与rejecte函数
  }
}

2、resolve和rejecte函数

前置知识

  1. 这两个函数是用于改变当前promise状态的。
  2. promise状态只有两种变化。pending-->fulfilled或pending-->rejected
  3. resolve调用promise状态从pending变为fulfilled,rejected调用promise状态从pending变为rejected。

class MyPromise {
      //Promise三种状态
  static PENDING = "pending";
  static FULLFILLED = "fullfilled";
  static REJECTED = "rejected";
  constructor(fn) {
    this.PromiseState = MyPromise.PENDING;//    当前promise的状态
    this.PromiseResult = undefined;//    promise收到的参数
    /* 
      这里由于resolve和rejecte函数是传到外部使用的,this会指向undefined。
      导致resovle和rejecte无法通过this修改promise实例的状态
      所以在传出去的时候先要绑定一下this,让它一直指向当前promise实例
    */
    const RESOLVE = this.resolve.bind(this);
    const REJECT = this.reject.bind(this);
    fn(RESOLVE,REJECT);//    传入的函数立即执行
  }

  resolve(res) {
    // 状态只能由pending变为其他,如果不是pending,说明状态已经改变,不再往下执行。
    if (this.PromiseState !== MyPromise.PENDING) return;
    //改变状态
    this.PromiseState = MyPromise.FULLFILLED;
    //传递参数
    this.PromiseResult = res;
  }

  reject(res) {
    if (this.PromiseState !== MyPromise.PENDING) return;
    this.PromiseState = MyPromise.REJECTED;
    this.PromiseResult = res;
  }
}

这里可以测试一下,resolve和rejected函数传出去时不绑定this会怎样

 输出结果,this指向undefined,导致resolve或rejecte内部无法通过this访问到promise实例

 

 如果在new Promise时直接抛出错误

promise的状态会直接变为rejected

 所以我们可以在函数内添加try catch捕捉错误,只要捕捉到错误直接调用rejecte函数

3、then函数

 前置知识:

  • then接收两个参数,一个是成功的回调函数,一个是失败的回调函数。两个参数都是函数。
  • 成功的回调函数的参数为resolve传入的值,失败的回调函数是rejecte传入的值。
  then(onFulfilled, onRejected) {
    // 先判断传入的值是否为函数,如果不是先包装为函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
    onRejected = typeof onRejected === "function" ? onRejected : () => {};
    if (this.PromiseState === MyPromise.FULLFILLED) {
      // 如果状态为fulfill就调用onFulfilled函数,并将结果传入
      onFulfilled(this.PromiseResult);
    } else if (this.PromiseState === MyPromise.REJECTED) {
      // 如果状态为fulfill就调用onrejected函数,并将结果传入
      onRejected(this.PromiseResult);
    }
  }

 但是这有一个问题,如果在new Promise的时候使用了定时器,会发生什么? 

 

 输出结果

有定时器的then里的东西并没有输出,这是因为定时器setTimeout是宏任务,而我们目前的这个地方then函数还是以同步的方式去执行的,当setTimeout执行,状态改变时,then已经执行完了,then执行的时候promise的状态还是pending,所以then内的回调函数并没有执行。

 我们需要一个数据结构存放pending状态的promise,等到promise状态改变了才通知then执行。这样就不会让then在promise还是pending的时候执行。

可以创建两个队列:onfulfilledCallback和onRejectedCallback存放状态为pending的promise

class MyPromise {
  static PENDING = "pending";
  static FULLFILLED = "fullfilled";
  static REJECTED = "rejected";
  constructor(fn) {
    this.PromiseState = MyPromise.PENDING;
    this.PromiseResult = undefined;
    try {
      /* 
      这里由于resolve和rejecte函数是传到外部使用的,this会指向外部作用域。
      导致resovle和rejecte无法通过this修改promise实例的状态
      所以在传出去的时候先要绑定一下this,让它一直指向当前promise实例
    */
      const RESOLVE = this.resolve.bind(this);
      const REJECT = this.reject.bind(this);
      this.onFullfilledCallback = []; //保存成功的回调
      this.onRejectedCallback = []; //保存失败的回调
      fn(RESOLVE, REJECT);
    } catch (err) {
      this.reject(err);
    }
  }

  resolve(res) {
    if (this.PromiseState !== MyPromise.PENDING) return;
    this.PromiseState = MyPromise.FULLFILLED;
    this.PromiseResult = res;
    while (this.onFullfilledCallback.length) {
      this.onFullfilledCallback.shift()(this.PromiseResult);
    }
  }

  reject(res) {
    if (this.PromiseState !== MyPromise.PENDING) return;
    this.PromiseState = MyPromise.REJECTED;
    this.PromiseResult = res;
    while (this.onRejectedCallback.length) {
      this.onRejectedCallback.shift()(this.PromiseResult);
    }
  }

  then(onFulfilled, onRejected) {
    // 先判断传入的值是否为函数,如果不是先包装为函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
    onRejected = typeof onRejected === "function" ? onRejected : () => {};
    if (this.PromiseState === MyPromise.FULLFILLED) {
      // 如果状态为fulfill就调用onFulfilled函数,并将结果传入
      onFulfilled(this.PromiseResult);
    } else if (this.PromiseState === MyPromise.REJECTED) {
      // 如果状态为fulfill就调用onrejected函数,并将结果传入
      onRejected(this.PromiseResult);
    } else if (this.PromiseState === MyPromise.PENDING) {
      // 将pending的promise装入队列中
      this.onFullfilledCallback.push(onFulfilled.bind(this));
      this.onRejectedCallback.push(onRejected.bind(this));
    }
  }
}

等到resovle或者reject调用,即promise状态确定的时候,再取出来进行调用。

链式调用

链式调用就是then之后还能继续调用then。那么哪里有then方法呢?肯定是在promise里。所以说只要then执行后返回一个新的promise就能继续then下去了。

then(onFulfilled, onRejected) {
    // 先判断传入的值是否为函数,如果不是先包装为函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
    onRejected = typeof onRejected === "function" ? onRejected : () => {};
    const newPromise = new MyPromise(() => {
      
      };
      if (this.PromiseState === MyPromise.FULLFILLED) {
        // 如果状态为fulfill就调用onFulfilled函数,并将结果传入
        onFulfilled(this.PromiseResult);
      } else if (this.PromiseState === MyPromise.REJECTED) {
        // 如果状态为fulfill就调用onrejected函数,并将结果传入
        onRejected(this.PromiseResult);
      } else if (this.PromiseState === MyPromise.PENDING) {
        // 将pending的promise装入队列中
        this.onFullfilledCallback.push(onFulfilled.bind(this));
        this.onRejectedCallback.push(onRejected.bind(this));
      }
    });
    return newPromise;
  }

返回值处理

在then的回调函数内可以返回值或者抛出错误,我们需要对这些返回值进行处理。

promise对返回值的处理是这样的

  • 返回自身-->报错
  • 返回一个promise --> 状态由该promise决定
  • 返回一个非promise的值 --> 包装为一个fulfilled状态的promise
  • 抛出错误 --> promise状态为rejected
const handleReturn = (callback) => {
        try {
          const returnVal = callback(this.PromiseResult);
          if (returnVal === newPromise) {
            throw new Error("不能返回自身");
          } else if (returnVal instanceof MyPromise) {
            returnVal.then(resolve, reject);
          } else {
            resolve(returnVal);
          }
        } catch (error) {
          reject(error);
          throw new Error(error);
        }

微任务调用

这里我还没有想出模拟微任务的方法,暂时先使用宏任务模拟微任务。我的想法是在resolve或rejecte方法内添加上定时器,让then内的回调函数的调用变为宏任务。

整体代码

class MyPromise {
  static PENDING = "pending";
  static FULLFILLED = "fullfilled";
  static REJECTED = "rejected";
  constructor(fn) {
    this.PromiseState = MyPromise.PENDING;
    this.PromiseResult = undefined;
    this.reutrnVal = null;
    try {
      /* 
      这里由于resolve和rejecte函数是传到外部使用的,this会指向外部作用域。
      导致resovle和rejecte无法通过this修改promise实例的状态
      所以在传出去的时候先要绑定一下this,让它一直指向当前promise实例
    */
      const RESOLVE = this.resolve.bind(this);
      const REJECT = this.reject.bind(this);
      this.onFullfilledCallback = []; //保存成功的回调
      this.onRejectedCallback = []; //保存失败的回调
      fn(RESOLVE, REJECT);
    } catch (err) {
      this.reject(err);
    }
  }

  resolve(res) {
    setTimeout(() => {
      if (this.PromiseState !== MyPromise.PENDING) return;
      this.PromiseState = MyPromise.FULLFILLED;
      this.PromiseResult = res;
      while (this.onFullfilledCallback.length) {
        this.onFullfilledCallback.shift()();
      }
    });
  }

  reject(res) {
    setTimeout(() => {
      if (this.PromiseState !== MyPromise.PENDING) return;
      this.PromiseState = MyPromise.REJECTED;
      this.PromiseResult = res;
      while (this.onRejectedCallback.length) {
        this.onRejectedCallback.shift()();
      }
    });
  }

  then(onFulfilled, onRejected) {
    // 先判断传入的值是否为函数,如果不是先包装为函数
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {};
    onRejected = typeof onRejected === "function" ? onRejected : () => {};
    const newPromise = new MyPromise((resolve, reject) => {
      const handleReturn = (callback) => {
        try {
          const returnVal = callback(this.PromiseResult);
          if (returnVal === newPromise) {
            throw new Error("不能返回自身");
          } else if (returnVal instanceof MyPromise) {
            returnVal.then(resolve, reject);
          } else {
            resolve(returnVal);
          }
        } catch (error) {
          reject(error);
          throw new Error(error);
        }
      };
      if (this.PromiseState === MyPromise.FULLFILLED) {
        // 如果状态为fulfill就调用onFulfilled函数,并将结果传入
        onFulfilled(this.PromiseResult);
      } else if (this.PromiseState === MyPromise.REJECTED) {
        // 如果状态为fulfill就调用onrejected函数,并将结果传入
        onRejected(this.PromiseResult);
      } else if (this.PromiseState === MyPromise.PENDING) {
        // 将pending的promise装入队列中
        this.onFullfilledCallback.push(handleReturn.bind(this, onFulfilled));
        this.onRejectedCallback.push(handleReturn.bind(this, onRejected));
      }
    });
    return newPromise;
  }
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值