or

手动实现一个Promise

2019年8月11日
这是一篇还未写完的文章。

Promise是es6出现的一个新对象类型,在处理异步操作时,他的登场频率太高太高啦。用了这么久promise,是时候研究研究它的“内部”了,当然这里不是指去看源码,而是自己手动仿造一个Promise对象来感受感受😆。下面会循序渐进地实现一个MyPromise类:

P1:在实例化一个Promise时,传入的函数参数会立刻被执行:

// 我的实现
class MyPromise {
  constructor(executor) {
    executor();
  }
}

// 会立刻输出1
const p = new MyPromise(() => {
  console.log(1);
})

P2:当promise的executor里调用resolve方法后,会执行then方法:

// 我的实现
class MyPromise {
  constructor(executor) {
    this._resolveHandlers = []; // 关于这里为何要用数组而不直接是一个变量来存储handler,原因可见下方“P3”

    // 此处如果写成下面这样,在实际调用resolve时会报错。
    // 原因:由于传给executor的_resolve方法里有用到this,在实际使用时,如上面的resolve(1),调用该方法的this默认是全局对象,而不是promise对象本身。
    // executor(this._resolve);
    // 因此需要手动进行绑定
    executor(this._resolve.bind(this));
  }

  then(resolveHandler) {
    this._resolveHandlers.push(resolveHandler);
  }

  // 命名习惯:私有变量用下划线做前缀
  _resolve(value) {

    // 如果_resolve的函数体写成这种,则then方法会失效,原因:
    // 由于executor体是同步执行的,执行到resolve时,然后开始执行下面的循环,此时由于then方法还未执行,_resolveHandlers数组还是空的,所以下面的循环并未执行
    // executor执行完了,然后执行then方法,此时会给_resolveHandlers添加resolve时的处理函数。
    // then也执行完了,然后改promise也完了,所以我们的resolve时的处理函数呢?它还没上台就下台了呀。
    /*
      while(this._resolveHandlers.length > 0) {
        this._resolveHandlers.pop()(value);
      }
    */

    // 为了解决上面的情况,能想到办法就是让_resolve函数体变成异步的。在浏览器环境下,setTimeout可以帮我们做到。(这里涉及到了event loop相关的知识啦,各位看官可以自行狗狗学习相关知识呦)
    // 当包裹setTimeout后,执行_resolve时,里面的内容会被放到队列里,等待其它的同步代码(这里指我们的then方法)执行完后再来执行这个while循环,此时_resolveHandlers里就有内容啦。
    setTimeout(() => {
      while(this._resolveHandlers.length > 0) {
        this._resolveHandlers.pop()(value);
      }
    }, 0)
  }
}

const p = new MyPromise((resolve) => {
  console.log(1)
  resolve(2);
  console.log(3);
}).then(value => {
  console.log(value);
})
// 上述会输出 1 3 2

P3: 同一promise可被多次then

// MyPromise实现同“P2”
// 从下面示例可看出,一个promise可能会有多个handler,所以上面“P2”中要用一个数组来存储handler

const p = new MyPromise((resolve) => {
  console.log(1)
  resolve(2);
  console.log(3);
});
p.then(value => {
  console.log(`handler1-${value}`);
})
p.then(value => {
  console.log(`handler2-${value}`);
})
// 上述会输出 1 3 handler1-2 handler2-2

P4:promise可被链式调用

class MyPromise {
  constructor(executor) {
    this._resolveHandlers = []; // 本例中,该变量不再是存储函数,而是对象,见下方then函数体
    executor(this._resolve.bind(this));
  }

  then(resolveHandler) {
    // 因为要被链式调用,则then应返回一个MyPromise对象,
    // 因为每个Promise对象的状态都要是独立的,所以不应直接返回this,而是新建一个对象
    const p = new MyPromise(() => {});
    // 当前对象需要保存自身的resolveHandler和新建的对象,会在_resolve中用到
    this._resolveHandlers.push({
      promise: p,
      handler: resolveHandler
    });
    return p;
  }

  _resolve(value) {
    setTimeout(() => {
      while(this._resolveHandlers.length > 0) {
        // 获取当前promise对象的resolveHandler和用于链式调用的新对象
        const { promise, handler } = this._resolveHandlers.shift(); // 使用shift而非pop,因为resolveHandler要满足先进先出
        // 执行handler获取返回值
        const ret = handler(value);
        // 如果返回值是个MyPromise对象,则需要先获取到他的resolve值(通过调用then获取),然后才能继续往后执行        
        if(ret instanceof MyPromise) {
          ret.then((val) => {
            // 温馨提示:可能会突然想不明白,新建的那个promise的resolveHandler哪里冒出来的呢? 别忘了,在不停的then then then调用时,这些都是同步执行的,所以resolveHandler早就提前保存好啦。 
            promise._resolve(val)
          })
        }
        else {
          // 如果返回值不是MyPromise类型,则直接resolve新建的那个promise来让它的resolveHandler执行。
          // 温馨提示:同上面那个温馨提示
          promise._resolve(ret)
        }
      }
    }, 0)
  }
}

const p = new MyPromise((resolve) => {
  resolve(2);
}).then(value => {
  console.log(value);
  return new MyPromise((resolve) => {
    resolve(3);
  })
}).then(value => {
  console.log(value);
  return 4;
}).then(value => {
  console.log(value);
}).then(value => {
  console.log(value);
})
// 上述输出 2 3 4 undefined

P5:

// MyPromise代码同P4


const p = new MyPromise((resolve) => {
  resolve(2);
})
p.then(value => {
  console.log(value); // 2
  p.then(val => {
    console.log(val); // 没有被执行
  }) 
})
p.then(val => {
  console.log(val); // 2
}) 

上述代码,外面的两个then会自上而下被同步执行,但里面那个then却没有,为啥呢?

因为resolveHandler的注册是在首次js从上往下执行时完成的,此时外面俩then的handler会随着resolve的执行而被调用,但里面那个then是在resolve执行后被执行然后注册的,此时resolve已经不会再次被执行了,所以里面那个then也就不会被执行了。

但我们期望的是里面那个then也会被执行,该怎么做呢?答案是新增一个状态变量,也就是原生Promise的三种状态Pending、Resolved、Rejected(我们目前暂不考虑reject情况)

class MyPromise {
  constructor(executor) {
    this._resolveHandlers = [];
    this._status = "pending"; // promise的初始状态是pending
    executor(this._resolve.bind(this));
  }

  then(resolveHandler) {
    const p = new MyPromise(() => {});
    this._resolveHandlers.push({
      promise: p,
      handler: resolveHandler
    });
    
    // 现在每当调用then,都会进行判断,如果已resolve,则会 
    if(this._status === 'resolved') {
      this._runResolveHandlers();
    }
    return p;
  }

  _resolve(value) {
    this._value = value;
    this._status === 'resolved';
    this._runResolveHandlers();
  }
  _runResolveHandlers() {
    setTimeout(() => {
      while(this._resolveHandlers.length > 0) {
        const { promise, handler } = this._resolveHandlers.shift();
        const ret = handler(this._value);
        if(ret instanceof MyPromise) {
          ret.then((val) => {
            promise._resolve(val)
          })
        } else {
          promise._resolve(ret)
        }
      }
    }, 0)
  }
}

const p = new MyPromise((resolve) => {
  resolve(2);
})
p.then(value => {
  console.log(value);
  p.then(val => {
    console.log(`hi-${val}`);
  })
})
p.then(val => {
  console.log(`hello-${val}`);
})
// 上述输出 2 hello-2 hi-2

P6:多次调用resolve,仅第一次起作用

// 修改_resolve方法即可
//...
  _resolve(value) {
    if(this._status === 'pending') {
      this._value = value;
      this._status = 'resolved';
      this._runResolveHandlers();
    } 
  }
//...

至此,关于resolve部分的实现算是OK啦,接下来是reject部分(暂无,目前心情很是糟糕😭)。

下面是最终代码(很多情况未处理,测试用例也不完全,仅供参考)

class MyPromise {
  constructor(executor) {
    this._resolveHandlers = [];
    this._rejectHandlers = [];
    this._status = "pending";
    this._value = undefined;
    this._rejectReason;

    // executor内也可能会出错,若出错,则catch
    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (e) {
      this._reject(e);
    }
  }

  then(resolveHandler, rejectHandler) {
    const p = new MyPromise(() => {});
    if(this._status === 'pending') {
      if(typeof resolveHandler === 'function') {
        this._resolveHandlers.push({
          promise: p,
          handler: resolveHandler
        });
      }
      if(typeof rejectHandler === 'function') {
        this._rejectHandlers.push({
          promise: p,
          handler: rejectHandler
        });
      }
    }

    if(this._status === 'resolved') {
      this._resolve();
    }
    if(this._status === 'rejected') {
      this._reject();
    }

    return p;
  }
  _resolve(value) {
    setTimeout(() => {
      this._value = value;
      this._status = 'resolved';
      while(this._resolveHandlers.length > 0) {
        const { promise, handler } = this._resolveHandlers.shift();
        const ret = handler(this._value);
        if(ret instanceof MyPromise) {
          ret.then((val) => {
            promise._resolve(val);
          }).catch((err) => {
            promise._reject(err);
          })
        } else {
          promise._resolve(ret)
        }
      }
      /*
        // 这种情况时,该promise的resolveHandlers是空的,此时需要遍历rejectHandlers来进行value的传递
       new MyPromise((resolve, reject) => {
        resolve(2);
       }).catch(val=> {
        return 44;
       })
      */
      while(this._rejectHandlers.length > 0) {
        const { promise } = this._rejectHandlers.shift();
        promise._resolve(value)
      }
    }, 0)
  }

  catch(rejectHandler) {
    return this.then(undefined, rejectHandler);
  }
  _reject(reason) {
    setTimeout(() => {
      this._rejectReason = reason;
      this._status = 'rejected';
      while(this._rejectHandlers.length > 0) {

        const { promise, handler } = this._rejectHandlers.shift();

        const ret = handler(this._rejectReason);
        if(ret instanceof MyPromise) {
          ret.then((val) => {
            promise._resolve(val)
          })
        } else {
          promise._resolve(ret)
        }
      }
      /*
        // 这种情况时,该promise的rejectHandlers是空的,此时需要遍历resolveHandlers来进行reason的传递
       new MyPromise((resolve, reject) => {
        reject(2);
       }).then(val=> {
        return 44;
       })
      */
      while(this._resolveHandlers.length > 0) {
        const { promise } = this._resolveHandlers.shift();
        promise._reject(reason)
      }
    }, 0)
  }
}
or