手写Promise核心代码

672 阅读11分钟

前言: ECMAScript6新增的引用类型 Promise,可以通过 new 操作符来实例化。具体使用方法想必大家早已烂熟于心,这里就不做过多介绍了,下面我们自己来实现一个 Promise。

1、声明MyPromise类

我们新建一个index.html和一个MyPromise.js文件,然后在MyPromise.js文件中定义类 MyPromise

index.html:

<!DOCTYPE html>
<html>
<head>
  <title></title>
  <script type="text/javascript" src="./MyPromise.js"></script>
</head>
<body>

</body>
</html>

MyPromise.js:

class MyPromise {
	
}

大家知道,Promise在用的时候,会传递一个执行者,如下:

new Promise((resolve, reject) => {

})

所以我们自己的也要传递这样一个执行者:

class MyPromise {
  constructor (executor) {

  }
}

大家又知道,Promise有三个内部状态:pending,fulfilled,rejected,这三个状态值是固定的,所以我们可以把这三个状态定义为MyPromise的静态属性,并且起初默认状态是pending,Promise最终是要调用then方法来处理拿到的值,所以要有一个接受这个值的变量,最后是如下

class MyPromise {
  static PENDING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECTED = 'rejected';
  constructor (executor) {
    // 默认状态是pending
    this.status = MyPromise.PENDING;
    // 用来接收值
    this.value = null;
  }
}

然后在使用的时候,会传入一个执行者函数,那么我们的MyPromise中肯定会执行这个函数,也就是executor,这个函数接收两个参数,resolvereject,这两个参数也是函数,resolve是成功时调用的,reject是失败时调用的,这两个函数我们可以抽出来写成类方法,调用resolve时我们会改变MyPromise的值和状态,调用reject时我们会拿到失败的信息。如下:

class MyPromise {
  static PENDING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECTED = 'rejected';
  constructor (executor) {
    // 默认状态是pending
    this.status = MyPromise.PENDING;
    // 用来接收值
    this.value = null;
    // 调用执行者函数,注意this的指向
    executor(this.resolve.bind(this), this.reject.bind(this))
  }
  resolve (value) {
    // 改变状态
    this.status = MyPromise.FULFILLED;
    // 改变值
    this.value = value;
  }
  reject (reason) {
    this.status = MyPromise.REJECTED;
    this.value = reason;
  }
}

写到这里我们运行一下看看效果,在index.html中调用一下:

<script type="text/javascript">
  const p = new MyPromise((resolve, reject) => {
    resolve("解决");
  })
  console.log(p)
</script>

控制台打印一下我们发现似乎达到了我们想要的结果:

image.png

但接着往下看,如果我在调用resolve后接着调用reject呢?

<script type="text/javascript">
  const p = new MyPromise((resolve, reject) => {
    resolve("解决");
    reject("失败");
  })
  console.log(p)
</script>

结果如下:

image.png

这显然是不对的,我们知道Promise的状态只能改变一次,也就是说我从pending状态改为fulfilled状态后,这个就不会变了,下面我们来看怎么锁定状态。

2、状态保护和添加异步捕获

其实很简单的啦,我们在resolve和reject方法中添加判断,就能起到状态保护的效果啦,代码一看就懂:

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.value = reason;
  }
}

我们在调用resolve函数之前,肯定还会写一些其他的代码,那么要是这个时候这些代码出错了,Promise会直接走reject。显然,我们自己写的这个目前还不行,来看:

<script type="text/javascript">
  const p = new MyPromise((resolve, reject) => {
    console.log(a)
    resolve("解决");
    reject("拒绝");
  })
  console.log(p)
</script>

控制台打印:

image.png

解决办法很简单,我们用try/catch来处理:

constructor (executor) {
  // 默认状态是pending
  this.status = MyPromise.PENDING;
  // 用来接收值
  this.value = null;
  try {
    // 调用执行者函数,注意this的指向
    executor(this.resolve.bind(this), this.reject.bind(this))
  } catch (error) {
    this.reject(error)
  }
}

这是控制台打印:

image.png

all right,我们继续往下走吧。

3、构建then

Promise有then方法,then方法接收两个参数,第一个参数是成功后要执行的函数,其参数是要处理的值,第二个参数是失败后要执行的函数,其参数是失败信息,如下:

new Promise((resolve, reject) => {
  resolve("解决");
  reject("拒绝");
}).then( value => {

}, reason => {

})

下面我们为MyPromise添加then方法,这里要注意两点:

1、只有状态改变,才会调用对应的方法;

2、then的两个参数是可以不传的,不能报错;

...
then (onFulfilled, onRejected) {
  // 如果不传,自己创建
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {}
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {}
  }
  // 只有状态改变时,才调用
  if (this.status === MyPromise.FULFILLED) {
    onFulfilled(this.value)
  }
  if (this.status === MyPromise.REJECTED) {
    onRejected(this.value)
  }
}

在index.html中测试一下:

<script type="text/javascript">
  new MyPromise((resolve, reject) => {
    resolve("解决");
  }).then( value => {
    console.log(value)
  })
  new MyPromise((resolve, reject) => {
    reject("拒绝");
  }).then( null, reason => {
    console.log(reason)
  })
</script>

控制台打印:

image.png

ok,看似没有问题,但是这里有两个问题还需要我们去解决:

1、在then的第一个参数中我们执行代码,如果报错,这时应该把错误抛到第二个参数中;

2、现在的代码还是同步执行的,没有起到异步的效果,我在MyPromise后写一个console.log('111'),大家会发现控制台先打印 '解决' ,再打印 '111'。

解决上述两个问题也很简单,我们借助try/catch和异步队列setTimeout,如下:

...
then (onFulfilled, onRejected) {
  // 如果不传,自己创建
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {}
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {}
  }
  // 只有状态改变时,才调用
  if (this.status === MyPromise.FULFILLED) {
    setTimeout(() => {
      try {
        onFulfilled(this.value)
      } catch (error) {
        onRejected(error)
      }
    })
  }
  if (this.status === MyPromise.REJECTED) {
    setTimeout(() => {
      try {
        onRejected(this.value)
      } catch (error) {
        onRejected(error)
      }
    })
  }
}

好的,这时候发现和我们预想的一样了,再次但是,有一种情况大家可能还没想到,就是我再MyPromise中使用异步的时候,会发生什么情况?

<script type="text/javascript">
  new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve("解决");
    }, 1000)
  }).then( value => {
    console.log(value)
  })
  console.log('111')
</script>

控制台打印:

image.png

这个时候我们发现过了1秒后,并没有打印出 '解决',这是因为,在1秒之内,状态是pending,这时候我们的then方法还没有对这种状态做处理,也就是没有收集到这时候的需要执行的函数。好,我们在constructor中定义一个数组,在状态为pending的时候把以后要执行的函数放进去,等要执行的时候,从里边拿出来执行就可以了。这里注意要考虑状态异常的处理,看下代码吧,如下:

...
constructor (executor) {
  // 默认状态是pending
  this.status = MyPromise.PENDING;
  // 用来接收值
  this.value = null;
  // 用来接收以后要执行的函数
  this.callbacks = [];
  try {
    // 调用执行者函数,注意this的指向
    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;
    // 在callbacks中函数拿出来执行
    this.callbacks.map( callback => {
      callback.onFulfilled(value);
    })
  }
}
reject (reason) {
  if (this.status === MyPromise.PENDING) {
    this.status = MyPromise.REJECTED;
    this.value = reason;
    this.callbacks.map( callback => {
      callback.onRejected(reason);
    })
  }
}
then (onFulfilled, onRejected) {
  // 如果不传,自己创建
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {}
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {}
  }
  // 状态为pending的时候,收集要执行的函数
  if (this.status === MyPromise.PENDING) {
    this.callbacks.push({
      onFulfilled: value => {
        try {
          onFulfilled(value);
        } catch (error) {
          onRejected(error);
        }
      },
      onRejected: value => {
        try {
          onRejected(value);
        } catch (error) {
          onRejected(error);
        }
      },
    })
  }
  // 只有状态改变时,才调用
  if (this.status === MyPromise.FULFILLED) {
    setTimeout(() => {
      try {
        onFulfilled(this.value)
      } catch (error) {
        onRejected(error)
      }
    })
  }
  if (this.status === MyPromise.REJECTED) {
    setTimeout(() => {
      try {
        onRejected(this.value)
      } catch (error) {
        onRejected(error)
      }
    })
  }
}

这样就达到了我们预想的效果,控制台过1秒打印 '解决':

image.png

好了,我们接下来在一个场景下验证一下看看有没有别的bug,index.html如下:

<script type="text/javascript">
  new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve("解决");
      console.log('222')
    }, 1000)
  }).then( value => {
    console.log(value)
  })
  console.log('111')
</script>

这个时候我们不难分析出,打印顺序应该是 '111','222','解决',但是我们看下控制台输出:

image.png

这显然是不对的,来吧,展示,setTimeout解决:

...
resolve (value) {
  if (this.status === MyPromise.PENDING) {
    // 改变状态
    this.status = MyPromise.FULFILLED;
    // 改变值
    this.value = value;
    // 在callbacks中函数拿出来执行
    setTimeout(() => {
      this.callbacks.map( callback => {
        callback.onFulfilled(value);
      })
    })
  }
}
reject (reason) {
  if (this.status === MyPromise.PENDING) {
    this.status = MyPromise.REJECTED;
    this.value = reason;
    setTimeout(() => {
      this.callbacks.map( callback => {
        callback.onRejected(reason);
      })
    })
  }
}
...

3、实现链式操作

我们知道,Promise是可以链式调用的,.then().then()...,也就是说,then方法会返回一个Promise,并且这个Promise和上一个没有关系。其实这就是重复之前的逻辑,跟递归一样,不要想的太复杂,下面我们就返回呗:

...
then (onFulfilled, onRejected) {
  // 如果不传,自己创建
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {}
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {}
  }
  return new MyPromise((resolve, reject) => {
    // 状态为pending的时候,收集要执行的函数
      if (this.status === MyPromise.PENDING) {
        this.callbacks.push({
          onFulfilled: value => {
            try {
              let result = onFulfilled(value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          },
          onRejected: value => {
            try {
              let result = onRejected(value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          },
        })
      }
      // 只有状态改变时,才调用
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
           let result = onFulfilled(this.value);
           resolve(result);
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            let result = onRejected(this.value);
            resolve(result);
          } catch (error) {
            reject(error)
          }
        })
      }
   })
 }

以上就实现了链式调用,但还是有点问题,我.then().then( value => {})这样调用的时候,数据传不到第二个then中,原因就是第一个then没有传参的时候,自己定义的函数没有返回值,那返回就好了,如下:

...
then (onFulfilled, onRejected) {
  // 如果不传,自己创建
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => this.value;
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => this.value;
  }
  return new MyPromise((resolve, reject) => {
    // 状态为pending的时候,收集要执行的函数
      if (this.status === MyPromise.PENDING) {
        this.callbacks.push({
          onFulfilled: value => {
            try {
              let result = onFulfilled(value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          },
          onRejected: value => {
            try {
              let result = onRejected(value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          },
        })
      }
      // 只有状态改变时,才调用
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
           let result = onFulfilled(this.value);
           resolve(result);
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            let result = onRejected(this.value);
            resolve(result);
          } catch (error) {
            reject(error)
          }
        })
      }
   })
 }

接下来,我们要是在then中再次new一个MyPromise会这么样?代码如下:

<script type="text/javascript">
  new MyPromise((resolve, reject) => {
    resolve("解决");
  }).then( value => {
    return new MyPromise(resolve => {
      resolve('haha')
    })
  }).then(value => {
    console.log(value)
  })
</script>

控制台打印:

image.png

这显然是不对的,原生的Promise是打印出 'haha',而我们的这个是直接打印出了MyPromise,下面我们来处理下,判断类型就完事了,注意拒绝状态也是一样,如下标出 * 的地方:

...
then (onFulfilled, onRejected) {
  // 如果不传,自己创建
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => this.value;
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => this.value;
  }
  return new MyPromise((resolve, reject) => {
    // 状态为pending的时候,收集要执行的函数
      if (this.status === MyPromise.PENDING) {
        this.callbacks.push({
          onFulfilled: value => {
            try {
              let result = onFulfilled(value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          },
          onRejected: value => {
            try {
              let result = onRejected(value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          },
        })
      }
      // 只有状态改变时,才调用
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
           let result = onFulfilled(this.value);
           // 判断值是不是MyPromise------******
           if (result instanceof MyPromise) {
             result.then(value => {
               resolve(value);
             }, reason => {
               reject(reason);
             })
           } else {
             resolve(result);
           }
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            let result = onRejected(this.value);
            resolve(result);
          } catch (error) {
            reject(error)
          }
        })
      }
   })
 }

下面我们把代码稍微优化一下,同时把状态为准备和拒绝的也加上,如下标出 * 的地方::

...
then (onFulfilled, onRejected) {
  // 如果不传,自己创建
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => this.value;
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => this.value;
  }
  return new MyPromise((resolve, reject) => {
    // 状态为pending的时候,收集要执行的函数
      if (this.status === MyPromise.PENDING) {
        this.callbacks.push({
          onFulfilled: value => {
            try {
              let result = onFulfilled(value);
              // ******
              if (result instanceof MyPromise) {
                result.then(resolve, reject)
              } else {
                resolve(result);
              }
            } catch (error) {
              reject(error);
            }
          },
          onRejected: value => {
            try {
              let result = onRejected(value);
              // ******
              if (result instanceof MyPromise) {
                result.then(resolve, reject)
              } else {
                resolve(result);
              }
            } catch (error) {
              reject(error);
            }
          },
        })
      }
      // 只有状态改变时,才调用
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
           let result = onFulfilled(this.value);
           // 判断值是不是MyPromise
           if (result instanceof MyPromise) {
              // 优化后------******
              result.then(resolve, reject)
            } else {
              resolve(result);
            }
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            let result = onRejected(this.value);
            // ******
            if (result instanceof MyPromise) {
              result.then(resolve, reject)
            } else {
              resolve(result);
            }
          } catch (error) {
            reject(error)
          }
        })
      }
   })
 }

ok,到这里then的实现基本告一段落了,大家可能发现了,上面重复代码很多,下面我们来优化一下。

4、then相关代码优化

我们发现三个状态中的代码都是重复的,那么我们在外边定义一个方法 parse 来优化一下,把重复代码放进去。还要处理一下就是不能返回Promise本身,代码如下:

...
parse (promise, result, resolve, reject) {
  if (promise === result) {
    throw new TypeError('Chaining cycle detected');
  }
  try {
    if (result instanceof MyPromise) {
      result.then(resolve, reject)
    } else {
      resolve(result);
    }
  } catch (error) {
    reject(error)
  }
}

然后then方法中如下:

...
then (onFulfilled, onRejected) {
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => this.value;
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => this.value;
  }
  let promise =  new MyPromise((resolve, reject) => {
    if (this.status === MyPromise.PENDING) {
      this.callbacks.push({
        onFulfilled: value => {
          this.parse(promise, onFulfilled(value), resolve, reject);
        },
        onRejected: value => {
          this.parse(promise, onRejected(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;
}

ok,至此代码优化完成了,让我们来实现一下其他的方法。

5、实现resolve与reject

看下面的代码:

Promise.resolve("解决").then(value => {
  console.log(value)
});
Promise.reject("拒绝").then(null, reason => {
  console.log(reason)
})

输出:

image.png

下面我们来实现,给MyPromise添加两个静态方法resolve和reject,具体代码如下:

...
static resolve (value) {
  return new MyPromise((resolve, reject) => {
    if (value instanceof MyPromise) {
      value.then(resolve, reject)
    } else {
      resolve(value);
    }
  })
}
static reject (value) {
  return new MyPromise((resolve, reject) => {
    reject(value);
  })
}

完成

6、实现all和race

  1. all方法,参数接受一个数组,数组中每一项都是一个Promise,当所有的Promise都成功后返回所有的值,有一个失败就返回失败的值:
let p1 = new Promise(resolve => {
  resolve('p1')
});
let p2 = new Promise(resolve => {
  resolve('p2')
});
Promise.all([p1, p2]).then(value => {
  console.log(value)
})

控制台打印:

image.png

同理,继续加静态方法:

...
static all (promises) {
  // 用来接收成功的值
  const values = [];
  return new MyPromise((resolve, reject) => {
    promises.forEach(promise => {
      promise.then(value => {
        values.push(value);
        // 当values中的数量和传进来的相等时,证明全部成功
        if (values.length === promises) {
          resolve(values);
        }
      }, reason => {
        reject(reason);
      })
    })
  })
}

race方法,参数接受一个数组,数组中每一项都是一个Promise,哪个最快就返回哪个值:

let p1 = new Promise(resolve => {
  setTimeout(() => {
    resolve('p1')
  }, 2000)
});
let p2 = new Promise(resolve => {
  setTimeout(() => {
    resolve('p2')
  }, 1000)
});
Promise.race([p1, p2]).then(value => {
  console.log(value)
})

控制台打印:

image.png

同理,继续加静态方法:

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

好了,至此Promise的大体实现就是这样了,下面是全部代码:

class MyPromise {

  static PENDING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECTED = 'rejected';
  
  constructor (executor) {
    // 默认状态是pending
    this.status = MyPromise.PENDING;
    // 用来接收值
    this.value = null;
    // 用来接收以后要执行的函数
    this.callbacks = [];
    try {
      // 调用执行者函数,注意this的指向
      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;
      // 在callbacks中函数拿出来执行
      setTimeout(() => {
        this.callbacks.map( callback => {
          callback.onFulfilled(value);
        })
      })
    }
  }
  
  reject (reason) {
    if (this.status === MyPromise.PENDING) {
      this.status = MyPromise.REJECTED;
      this.value = reason;
      setTimeout(() => {
        this.callbacks.map( callback => {
          callback.onRejected(reason);
        })
      })
    }
  }
  
  parse (promise, result, resolve, reject) {
    if (promise === result) {
      throw new TypeError('Chaining cycle detected');
    }
    try {
      if (result instanceof MyPromise) {
        result.then(resolve, reject)
      } else {
        resolve(result);
      }
    } catch (error) {
      reject(error)
    }
  }
  
  then (onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = () => this.value;
    }
    if (typeof onRejected !== 'function') {
      onRejected = () => this.value;
    }
    let promise =  new MyPromise((resolve, reject) => {
      if (this.status === MyPromise.PENDING) {
        this.callbacks.push({
          onFulfilled: value => {
            this.parse(promise, onFulfilled(value), resolve, reject);
          },
          onRejected: value => {
            this.parse(promise, onRejected(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;
  }
  
  static resolve (value) {
    return new MyPromise((resolve, reject) => {
      if (value instanceof MyPromise) {
        value.then(resolve, reject)
      } else {
        resolve(value);
      }
    })
  }
  
  static reject (value) {
    return new MyPromise((resolve, reject) => {
      reject(value);
    })
  }
  
  static all (promises) {
    // 用来接收成功的值
    const values = [];
    return new MyPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(value => {
          values.push(value);
          // 当values中的数量和传进来的相等时,证明全部成功
          if (values.length === promises) {
            resolve(values);
          }
        }, reason => {
          reject(reason);
        })
      })
    })
  }
  
  static race (promises) {
    return new MyPromise((resolve, reject) => {
      promises.map(promise => {
        promise.then(value => {
            resolve(value);
        }, reason => {
          reject(reason);
        })
      })
    })
  }
}

只是简单的实现了一下,还请各位大佬多多指正。