手写Promise

写了一年的Promise,今天终于是把测试跑通了,其它都不是难点,难点在于resolvePromise,这是所有里面最难的,当然我们抛开这个先不谈,先实现一些基本的特性.

1. Promise 的三个基本属性

Promise有三个基本属性

  1. value:Promise成功的值
  2. reason:Promise失败的理由
  3. state:状态,有pendingfulfilledrejected状态

所以我们可以添加三个属性值,以及关于State的三个状态的常量:

js 复制代码
const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value; // 成功的值
      reason; // 失败的理由
      state = State.pending;
 }

2. Promise 的构造器

Promise的构造器传入参数是一个函数,并传入resolvereject函数,用来改变Promise对象的状态,所以我们可以得到如下代码:

js 复制代码
const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this)); // 新增的代码
      } 
      
      resolve(){} // 新增的代码
      reject(){} // 新增的代码
 }

2. resolve和reject

Promise中调用这个函数,分别会将Promise的状态改为fulfilledrejected,所以加入状态的改变:

js 复制代码
const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value){
          this.value = value; // 新增的代码
          this.state = State.fulfilled; // 新增的代码
      }
      
      reject(reason){
         this.reason = reason; // 新增的代码
         this.state = State.rejected; // 新增的代码
      } 
 }

当然,Promise状态修改后,就不能再次修改,所以我们加入判断

js 复制代码
const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value){
          if (this.state !== State.pending) return; // 新增的代码

          this.value = value; 
          this.state = State.fulfilled; 
      } 
      
      reject(reason){
         if (this.state !== State.pending) return; // 新增的代码
        
         this.reason = reason; 
         this.state = State.rejected;
      } 
 }

3. then函数

then函数返回一个新的Promise对象,参数是两个函数,第一个函数是前一个promise对象被Resolve时调用,第二个函数是前一个对象被Reject时调用,所以我们加上一个then方法,并返回一个新的Promise,并且调用它的ResolveReject

js 复制代码
const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value){
          if (this.state !== State.pending) return;

          this.value = value; 
          this.state = State.fulfilled; 
      } 
      
      reject(reason){
         if (this.state !== State.pending) return;
        
         this.reason = reason; 
         this.state = State.rejected;
      } 
      
      // 新增代码
      then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
              const result = onFulfilled(this.value)
              resolve(result); 
          };

          onRejectedFn = function () {
              const result = onRejected(this.reason);
              resolve(result);
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        }
        
        return nextPromise;
     }
 }

promise对象resolve或者reject的时候,then函数不是立即执行的,也就是说是异步的,所以,我们需要在回调外面套上queueMircrotask

javascript 复制代码
    ... 省略
    
      then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
             queueMicrotask(()=>{
              const result = onFulfilled(this.value)
              resolve(result); 
             });
          };

          onRejectedFn = function () {
              queueMircotask(()=>{
               const result = onRejected(this.reason);
               resolve(result);
              })
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        }
        
        return nextPromise;
     }
  
    ... 省略

但是此时我们执行这个例子

js 复制代码
new MyPromise((resolve)=>{ setTimeout(()=>{ resolve(1) }) })
.then((res)=>{console.log(res)})

你会发现没有任何输出

这是因为刚才的then只能同步执行

所以我们需要用数组来保存订阅,并在状态发生变更的时候执行数组中的函数:

js 复制代码
...省略
class MyPromise {
  ...省略
  onFulfilledFnArray = [];
  onRejectedFnArray = []; // 为了可以 let a = Promise; a.then(); a.then()}
  ...省略
}
...省略

then变更为

ini 复制代码
...省略
 then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
             queueMicrotask(()=>{
              const result = onFulfilled(this.value)
              resolve(result); 
             });
          };

          onRejectedFn = function () {
              queueMircotask(()=>{
               const result = onRejected(this.reason);
               resolve(result);
              })
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        } else {
          // 新增代码
          this.onFulfilledFnArray.push(onFulfilledFn);
          this.onRejectedFnArray.push(onRejectedFn);
        }

        return nextPromise;
     }
...省略

修改一下resolvereject

js 复制代码
...省略
class MyPromise {
  ...省略
   resolve(value) {
        if (this.state !== State.pending) return;

        this.value = value;
        this.state = State.fulfilled;

        // 新增代码
        while (this.onFulfilledFnArray.length > 0) {
          this.onFulfilledFnArray.shift()(); // 不是立即执行
        }
  }

  reject(reason) {
        if (this.state !== State.pending) return;

        this.reason = reason;
        this.state = State.rejected;

        // 新增代码
        while (this.onRejectedFnArray.length > 0) {
          this.onRejectedFnArray.shift()(); // 不是立即执行
        }
  }

  ...省略
}
...省略

给出当前的完整代码:

js 复制代码
const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
      value;
      reason;
      onFulfilledFnArray = [];
      onRejectedFnArray = [];
      state = State.pending;
      
      constructor(exector){
         exector?.(this.resolve.bind(this), this.reject.bind(this));
      } 
      
      resolve(value) {
        if (this.state !== State.pending) return;

        this.value = value;
        this.state = State.fulfilled;

        // 新增代码
        while (this.onFulfilledFnArray.length > 0) {
          this.onFulfilledFnArray.shift()(); // 不是立即执行
        }
      }

      reject(reason) {
        if (this.state !== State.pending) return;

        this.reason = reason;
        this.state = State.rejected;

        // 新增代码
        while (this.onRejectedFnArray.length > 0) {
          this.onRejectedFnArray.shift()(); // 不是立即执行
        }
      }
      
      then(onFulfilled, onRejected) {
        let onFulfilledFn;
        let onRejectedFn;
        
        const nextPromise = new MyPromise((resolve, reject) => {
          onFulfilledFn = function () {
             queueMicrotask(()=>{
              const result = onFulfilled(this.value)
              resolve(result); 
             });
          };

          onRejectedFn = function () {
              queueMircotask(()=>{
               const result = onRejected(this.reason);
               resolve(result);
              })
          };
        });

        onFulfilledFn = onFulfilledFn.bind(this);
        onRejectedFn = onRejectedFn.bind(this);

        if (this.state === State.fulfilled) {
          onFulfilledFn();
        } else if (this.state === State.rejected) {
          onRejectedFn();
        } else {
          // 新增代码
          this.onFulfilledFnArray.push(onFulfilledFn);
          this.onRejectedFnArray.push(onRejectedFn);
        }

        return nextPromise;
     }
 }

可以发现成功输出1

resolve传递值的规则

  1. resolve不能传递同一个Promise对象,否则会报错 这种情况一般发生在异步代码中:
javascript 复制代码
const promise = new Promise((resolve, reject)=>{
   setTimeout(()=>{
       resolve(promise)
   })
})

Promise - JavaScript | MDN (mozilla.org)

所以我们需要写一个resolvePromise的函数,来过滤掉特殊情况

js 复制代码
resolvePromise(value) {
   if (value === this) {
      this.reject(
        new TypeError(
          'Circular reference detected: promise and x are the same object',
        ),
      );
      return false;
    }  
}
  1. thenable对象 当遇到thenable对象时,会调用thenable对象的then,并将resolve的值作为promise的最终状态。
js 复制代码
  resolvePromise(value) {
    let called = false;

    if (value === this) {
       // 省略
    } else if (value instanceof MyPromise) {
      try {
        value.then(
          x => {
            if (called) return;
            called = true;
            this.resolve(x);
          },
          y => {
            if (called) return;
            called = true;
            this.reject(y);
          },
        );
        return false;
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'object') {
      try {
        const thenable = Reflect.get(value, 'then');
        if (typeof thenable === 'function') {
          try {
            thenable.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'function') {
      try {
        if (Reflect.has(value, 'then')) {
          return this.resolvePromise(Reflect.get(value, 'then'));
        }
        if (value) {
          try {
            value.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    }

    return true;
  }

最后修改一下resolve函数:

kotlin 复制代码
  resolve(value) {
    if (this.state !== State.pending) return;
    if (!this.resolvePromise(value)) return;  // 特殊对象,特殊解析

    this.value = value;
    this.state = State.fulfilled;

    while (this.onFulfilledFnArray.length > 0) {
      this.onFulfilledFnArray.shift()(); // 不是立即执行
    }
  }

。。。还没想好怎么写,正在🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

先给出完整代码:

js 复制代码
const State = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected',
};

class MyPromise {
  value; // 成功的值
  reason; // 失败的理由
  state = State.pending;
  onFulfilledFnArray = [];
  onRejectedFnArray = []; // 为了可以 let a = Promise; a.then(); a.then()

  constructor(exector) {
    exector?.(this.resolve.bind(this), this.reject.bind(this));
  }

  static resolve(value) {
    return new MyPromise(resolve => {
      resolve(value);
    });
  }

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

  resolvePromise(value) {
    let called = false;

    if (value === this) {
      this.reject(
        new TypeError(
          'Circular reference detected: promise and x are the same object',
        ),
      );

      return false;
    } else if (value instanceof MyPromise) {
      try {
        value.then(
          x => {
            if (called) return;
            called = true;
            this.resolve(x);
          },
          y => {
            if (called) return;
            called = true;
            this.reject(y);
          },
        );
        return false;
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'object') {
      try {
        const thenable = Reflect.get(value, 'then');
        if (typeof thenable === 'function') {
          try {
            thenable.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    } else if (value && typeof value === 'function') {
      try {
        if (Reflect.has(value, 'then')) {
          return this.resolvePromise(Reflect.get(value, 'then'));
        }
        if (value) {
          try {
            value.call(
              value,
              x => {
                if (called) return;
                called = true;
                this.resolve(x);
              },
              y => {
                if (called) return;
                called = true;
                this.reject(y);
              },
            );
            return false;
          } catch (e) {
            if (called) return;
            this.reject(e);
            return false;
          }
        }
      } catch (e) {
        if (called) return;
        this.reject(e);
        return false;
      }
    }

    return true;
  }

  resolve(value) {
    if (this.state !== State.pending) return;
    if (!this.resolvePromise(value)) return;

    this.value = value;
    this.state = State.fulfilled;

    while (this.onFulfilledFnArray.length > 0) {
      this.onFulfilledFnArray.shift()(); // 不是立即执行
    }
  }

  reject(reason) {
    if (this.state !== State.pending) return;

    this.reason = reason;
    this.state = State.rejected;

    while (this.onRejectedFnArray.length > 0) {
      this.onRejectedFnArray.shift()(); // 不是立即执行
    }
  }

  catch(onFulfilled) {
    return this.then(undefined, onFulfilled);
  }

  then(onFulfilled, onRejected) {
    let onFulfilledFn;
    let onRejectedFn;
    const nextPromise = new MyPromise((resolve, reject) => {
      onFulfilledFn = function () {
        queueMicrotask(() => {
          try {
            if (typeof onFulfilled === 'function') {
              const result = onFulfilled(this.value);
              resolve(result);
            } else {
              resolve(this.value);
            }
          } catch (e) {
            reject(e);
          }
        });
      };

      onRejectedFn = function () {
        queueMicrotask(() => {
          try {
            if (onRejected) {
              if (typeof onRejected === 'function') {
                const result = onRejected(this.reason);
                resolve(result);
              } else {
                reject(this.reason);
              }
            } else {
              reject(this.reason);
            }
          } catch (e) {
            reject(e);
          }
        });
      };
    });

    onFulfilledFn = onFulfilledFn.bind(this);
    onRejectedFn = onRejectedFn.bind(this);

    if (this.state === State.fulfilled) {
      onFulfilledFn();
    } else if (this.state === State.rejected) {
      onRejectedFn();
    } else {
      this.onFulfilledFnArray.push(onFulfilledFn);
      this.onRejectedFnArray.push(onRejectedFn);
    }

    return nextPromise;
  }
}

代码仓库以及测试: RadiumAg/javascript: 学习JavaScript (github.com)

相关推荐
Maer098 分钟前
Cocos Creator3.x设置动态加载背景图并且循环移动
javascript·typescript
Good_Luck_Kevin20188 分钟前
速通sass基础语法
前端·css·sass
大怪v21 分钟前
前端恶趣味:我吸了juejin首页,好爽!
前端·javascript
反应热35 分钟前
浏览器的本地存储技术:从 `localStorage` 到 `IndexedDB`
前端·javascript
刘杭36 分钟前
在react项目中使用Umi:dva源码简析之redux-saga的封装
前端·javascript·react.js
某公司摸鱼前端39 分钟前
js 如何代码识别Selenium+Webdriver
javascript·selenium·测试工具·js
有一个好名字1 小时前
Vue Props传值
javascript·vue.js·ecmascript
pan_junbiao1 小时前
Vue使用axios二次封装、解决跨域问题
前端·javascript·vue.js
秋沐1 小时前
vue3中使用el-tree的setCheckedKeys方法勾选失效回显问题
前端·javascript·vue.js
浮华似水1 小时前
Yargs里的Levenshtein距离算法
前端