Skip to main content

promise源码实现

前言#

Promise,看到这个词,大脑里第一念头想到的是微任务、解决异步回调。但是在这上面栽过跟头,记得有次面试,面试官出了一个比较简单的和promise相关的问题,可惜平日写的少不怎么会运用,面试准备都刷leetcode去了,但是面试官一道也没问。当时也有些不在状态,面试官一再让我尝试,最后也没有写出来,最后只拿到了一个白菜的offer,有些痛心疾首。promise也在网上看到很多文章写过了,这两天沉下心实现了一下,关于核心的一些点,觉得应该整理一下。

产生原因#

promise是解决异步回调地狱的一个方案,回调地狱是指对于异步操作,我们不知道异步任务何时能完成,不能一直让单线程的js等着,所以提前把回调函数注册好,当异步耗时的任务结束后,就触发回调,执行提前写好的回调函数。但是当多个异步任务之间有依赖之后,C异步任务依赖于B异步任务,B异步任务依赖于A异步任务,就会导致代码越来越胖,宽度增加,也就是说回调会一层嵌套一层,按照正常的缩进,代码会冗长且胖。社区的讨论,时代的产物,promise诞生了。

解决的问题&关键原理#

promise比较优雅的一点是:将代码从宽度变为了长度,不需要一层一层嵌套回调,只需要从上往下按着写就好。回调函数还是可以提前注册好,不过不是水平方向了,而是写到promise的then函数里,这里使用了发布订阅的设计模式,当异步任务完成后会调用resolve,进而通知预先注册好的回调函数,依次执行。

好了,源码附在最后面,下面写一下实现源码关键与巧妙的点吧。

Promise的三种状态#

一个Promise只能三种状态,pendingfulfilledrejected,分别是等待着,已完成,已拒绝。promise在没有调用resolve方法和reject方法之前,就是pending状态,调用了resolve方法之后,转换为fulfilled状态,同理,调用了reject方法之后,转换为rejected状态。但是,pending只能转换为fulfilled或rejected状态的一种,且不可逆转。这是怎么实现的呢?一个标志位,status,调用resolvereject方法之后就进行标志位的赋值。

promise构造器的参数#

构造一个promise,输入的参数是一个函数x,这个函数会提供两个形参,名字随便设置,一般设为resolvereject,这两个方法实际上是在Promise内部实现的。当promise构造结束后,会直接调用这个函数x。

promise的then方法#

then方法的作用是:注册回调函数。

then方法接收的参数是两个函数onResolvedonRejected,这两个函数就是回调函数。就是好的情况和坏的情况两种。当promise对象按照预期的逻辑正常执行时,也就是执行到调用了resolve方法这里,就会执行onResolvd这个回调,如果预设的逻辑有问题,或者某处出错,会执行onReject这个回调。

then方法可以进行链式调用,因为then方法执行后返回的也是一个新的promise,同样可以执行then方法。

then方法实现的关键点。

  1. 输入的参数必须是函数。如果只是普通值,需要在源码内进行修饰,转换为函数。
  2. 一个promise对象的正确回调函数,也就是then方法内注册的第一个函数,它接受的参数是构造promise对象的函数内,执行resolve(abc)方法时传入的参数,也就是abc,有可能是一个值,此时就直接执行;也有可能是一个新的promise,此时需要等待新的promise执行完毕后,看返回值是什么再拿来当做参数。这就有可能子子孙孙无穷尽,不知道下一个参数是promise还是普通值,就需要递归来处理。
  3. 查看当前的promise状态是什么,如果是pending,就把注册的成功回调和失败回调放到相应的订阅者数组中,等待resolve或者reject一声令下,就执行回调。

isPromise方法#

判断一个对象是不是promise,一只动物会嘎嘎叫,就认定他是鸭子。一个对象有then方法,就认为他是promise。

Promise.all实现#

此方法输入是一个数组,数组内容是promise对象或者是普通值。输出是所有promise返回的结果。如果有promise出错,就直接拒绝。

构造一个新的promise来实现,一个比较关键的点是,要根据已经完成态promise的数量来决定是否执行resolve方法


源码如下:


const RESOLVED = 'RESOLVED';const PENDING = 'PENDING';const REJECTED = 'REJECTED';
class MyPromise {  constructor(fn) {    this.value = undefined;    this.reason = undefined;    this.status = PENDING;    this.onResolvedTasks = [];    this.onRejectedTasks = [];
    let resolve = (val) => {      if(this.status === PENDING) { // PENDING的作用,是让resolve和reject不能同时调用        this.status = RESOLVED;        this.value = val;        this.onResolvedTasks.forEach(cb => cb());      }    }
    let reject = (reason) => {      if(this.status === PENDING) {        this.status = REJECTED;        this.reason = reason;        this.onRejectedTasks.forEach(cb => cb());      }    }
    try {      fn(resolve, reject);    } catch (e) {      reject(e);    }  }
  then(onResolved, onRejected) {    onRejected = typeof onRejected === 'function'      ? onRejected      : reason => {throw reason};    onResolved = typeof onResolved === 'function'      ? onResolved      : v => v;        const promise2 = new MyPromise((resolve, reject) => {      let x;
      if(this.status === RESOLVED) {        setTimeout(() => {  // 这里需要使用异步,否则无法获得promise2          try {            x = onResolved(this.value);            this.resolvePromise(promise2, x, resolve, reject);          } catch (e) {            reject(e);          }        }, 0);      }
      if(this.status === REJECTED) {        setTimeout(() => {          try {            x = onRejected(this.reason);            this.resolvePromise(promise2, x, resolve, reject);          } catch(e) {            reject(e);          }        }, 0);      }
      if(this.status === PENDING) {        this.onResolvedTasks.push(          () => {            setTimeout(() => {              try {                x = onResolved(this.value);                this.resolvePromise(promise2, x, resolve, reject);              } catch (e) {                reject(e);              }            }, 0);          }        );                this.onRejectedTasks.push(          () => {            setTimeout(() => {              try {                x = onRejected(this.reason);                this.resolvePromise(promise2, x, resolve, reject);              } catch (e) {                reject(e);              }            })          }        );      }    })
    return promise2;  }
  catch(onRejected) {    return this.then(undefined, onRejected);  }
  resolvePromise(promise, x, resolve, reject) {    if(promise === x) {      reject(new TypeError('Chaining cycle detected for promise!'));    }    if(x && typeof x === 'object' || typeof x === 'function') {      let used = false; // 只调用一次      try {
        let then = x.then; // 如果x是promise,则取出他的then方法,        if(typeof then === 'function') {          then.call(x, y => { // 对前一个then返回是promise的情况,对返回的promise注册新的方法,继续取值            if(used) return;            used = true;            this.resolvePromise(promise, y, resolve, reject);          }, err => {            if(used) return;            used = true;            reject(err);          });        } else {          if(used) return;          used = true;          resolve(x); // x不是一个函数,是一个对象???        }              } catch (err) {        if(used) return;        used = true;        reject(err);      }    } else {      resolve(x); // 如果正常值的话直接resolve返回    }  }
  static isPromise(p) {    if(p&&typeof p==='object' || typeof p === 'function') {      if(typeof p.then === 'function') {        return true;      }    } else {      return false;    }  }  static all(promises) {    if(!Array.isArray(promises)) {      throw new TypeError('the input should be an array!');    }    return new MyPromise((resolve, reject) => {      let results = [];      let count = 0;      let length = promises.length;      function dealPromise(i, value) {        results[i] = value;        count += 1;        if(count===length) {          resolve(results);        }      }      for(let i=0; i<length; i++) {        if(this.isPromise(promises[i])) {          promises[i].then(value => {            dealPromise(i, value);          }, reason => {            reject(reason);            return ;          });        } else {          dealPromise(i, promises[i]);        }      }    });  }
  static race(promises) {    if(!Array.isArray(promises)) {      throw new TypeError('the input should be an array!');    }
    return new MyPromise((resolve, reject) => {      let length = promises.length;      for(let i=0; i<length; i++) {        if(this.isPromise(promises[i])) {          promises[i].then(val => {            resolve(val);            return ;          }, err => {            reject(err);            return ;          })        } else {          resolve(promises[i]);          return;        }      }    });  }
  static resolve(value) {    if(this.isPromise(value)) {      return value;    } else {      return new MyPromise((resolve, reject) => {        resolve(value);      });    }  }
  static reject(value) {    return new MyPromise((resolve, reject) => {      reject(value);    })  }}
// 可以使用promises-aplus-tests这个npm包来检测MyPromise.defer = MyPromise.deferred = function () {  let dfd = {};  dfd.promise = new MyPromise((resolve, reject) => {      dfd.resolve = resolve;      dfd.reject = reject;  });  return dfd;}
module.exports = MyPromise

可以使用promises-aplus-tests这个npm包来检测实现的是否正确。


一开始不想写大论文,最近渐入佳境,游戏也不想打了。

written by Rain

2021.1.30 天朗气清