简单实现 Promise

120 阅读6分钟

0x00 前言

Promise是前端异步回调的一个解决方案,更多的介绍其实网上都有。但是Promise规范有很多,如Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版Promise/A+。

现在简单按照 Promise/A+ 的规范手撸一个简单的Promise对象。

【翻译】Promises/A+规范

0x01 条件

首先我们需要了解一个简单的Promise至少需要满足什么条件。

  1. Promise 的实例是具有状态的,而状态包括:等待态(Pending)、执行态(Fulfilled)、拒绝态(Rejected)。
  2. 一个 Promise 必须提供一个 then 方法以访问其当前值、终值和据因。

0x02 实现

02.1 定义

首先我们通过一个立即执行匿名函数构建一个封闭的作用域,避免污染问题。

使用Promise的时候,我们都需要去创建一个新的实例,如:new Promise(...),所以我们需要最后返回的是一个Function

简单延伸一下,new做了什么操作:在Javascript中new是作为一个保留的关键词存在,其中new的操作是隐式的新建一个临时的空白对象,并拷贝了function.prototype的属性,最后将构造函数中的this指向这个临时新建的对象,最后返回这个临时对象

var PromiseA = (() => {
  const PENDING = 0; //等待态
  const FULFILLED = 1; //执行态
  const REJECTED = 2; //拒绝态
  
  function PromiseA(fn) {
    this.state = PENDING;
    this.handlers = [];
    fn(this._resolve.bind(this), this._reject.bind(this));
  }

  Object.assign(PromiseA.prototype,{...}) // 后续逐个解释

  return PromiseA;
})();

用于现在大部分浏览器都已经默认支持Promise对象,为了避免冲突所以另起PromiseA作为例子。

构造函数中默认赋值 this.state = PENDING,当实例新建的时候实例即处于等待状态。

由于规范中要求:then 方法可以被同一个 promise 调用多次。所以 this.handlers = [],用于在实例还处于PENDING状态时将多个 then 方法收集起来。

fn(this.resolve.bind(this), this.reject.bind(this)) 立即调用创建实例时传入的函数方法,并且将 resolve\reject 方法绑定当前 promise 实例后以参数形式传递。

到这里Promise的构造函数就写完了,接下来我们需要根据规范完成其他都应的方法。

02.2 promise.then

  • promise 的 then 方法接受两个参数:promise.then(onFulfilled, onRejected)
  • then 方法必须返回一个 promise 对象
/**
 * promise 的 then 方法
 **/
PromiseA.prototype.then(onFulfilled, onRejected) {
  return new PromiseA((resolve, reject) => {
    this._registerHandler(
      result => {
        try {
          if (onFulfilled && onFulfilled instanceof Function) {
            resolve(onFulfilled(result));
          } else {
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      },
      reason => {
        try {
          if (onRejected && onRejected instanceof Function) {
            resolve(onRejected(reason));
          } else {
            reject(reason);
          }
        } catch (err) {
          reject(err);
        }
      }
    );
  });
}

/**
 * 注册then的回调方法,如果是PENDING状态则添加到队列。
 * 如果是FULFILLED 、 REJECTED 则直接执行方法
 **/
PromiseA.prototype._registerHandler(onFulfilled, onRejected) {
  if (this.state === PENDING) {
    this.handlers.push({
      onFulfilled,
      onRejected
    });
  } else {
    setTimeout(() => {
      if (this.state === FULFILLED) {
        onFulfilled(this.result);
      } else {
        onRejected(this.reason);
      }
    }, 0);
  }
}

根据上面的两个条件,我们知道每个then方法的返回值是 promise 对象,所以直接创建一个新的 Promise。

this._registerHandler 这段代码中由于使用了箭头函数,所以 this 指向的是调用了 then 方法的 promise 实例,而非新创建的返回实例。this._registerHandler 将根据当前的 promise 实例状态判断执行不断的操作,如果当前状态是PENDING则将回调方法暂时存放在 handlers 中,如果是FULFILLED或者REJECTED则直接执行其中对应的回调。

this._registerHandler接受的第一个参数是成功后的回调方法,第二个则是失败的。

如果 onFulfilled 不是函数,其必须被忽略 如果 onRejected 不是函数,其必须被忽略

根据规则,如果判断这两个参数不是函数,则直接跳过处理。但有个地方需要注意的是,经过promise.then#onRejected的方法处理后如果没有抛出新的错误或者返回一个新的Promise.reject,接下来的状态则应该是FULFILLED

如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)

最后要说的是为什么setTimtou(,0),因为无论是静态值还是回调返回的值,当执行resolve后,then的回调方法都会放在微任务(mircoTask)队列中等待执行。所以这里简单用setTimeout模拟了。

02.3 _resolve _reject

经过上面的代码热身大家应该能有一点感觉了,但其最核心主要的还是构造函数中的fn(this._resolve.bind(this), this._reject.bind(this));这段代码。

PromiseA.prototype._resolve(result) {
  let then = result && this._getThen(result);
  if (then) {
    try {
      then.call(
        result,
        thenResult => {
          if (this.state !== PENDING) return;
          this._fulfill(thenResult);
        },
        thenReason => {
          if (this.state !== PENDING) return;
          this._reject(thenReason);
        }
      );
    } catch (e) {
      if (this.state !== PENDING) return;
      this._reject(e);
      debugger;
      console.error(e);
    }
  } else {
    this._fulfill(result);
  }
}

PromiseA.prototype._getThen(result) {
  let then = result.then;
  if (then && then instanceof Function) {
    return then;
  }
  return null;
}

PromiseA.prototype._fulfill(result) {
  if (this.state !== PENDING) return;
  this.state = FULFILLED;
  this.result = result; // 必须拥有一个不可变的终值

  // 调用回调函数
  this.handlers.forEach(handler => {
    handler.onFulfilled(result);
  });
}

PromiseA.prototype._reject(reason) {
  if (this.state !== PENDING) return;
  this.state = REJECTED;
  this.reason = reason; //必须拥有一个不可变的据因

  this.handlers.forEach(handler => {
    handler.onRejected(reason);
  });
}

首先我们看下规则:

x 为 Promise 如果 x 为 Promise ,则使 promise 接受 x 的状态 注4: 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝 如果 x 处于执行态,用相同的值执行 promise 如果 x 处于拒绝态,用相同的据因拒绝 promise

x指的是代码中的result

从规则中看到,我们需要在_resolve函数中判断返回值是否 Promise 对象,如果是的话需要等待这个promise直至被执行或者拒绝,这个时候我们需要对这个 promise 注册回调,当回调成功的时候则调用this._fulfill。如果不是 Promise 则直接调用 this._fulfill

再看 _getThen,其实只是满足其中规则中定义的thenable(是一个定义了 then 方法的对象或函数。)去获取then方法。

_fulfill_reject。判断当前实例状态是否PENDING,因为根据状态中的规则来看只有PENDING可以迁移至执行态或拒绝态,并且不可逆。

0x03 图解示例

  1. 创建一个新的Promise实例(promise1)时,创建的参数是一个函数,函数接收两个参数,分别是函数:resolve、reject。通过调用其中一个函数决定当前Promise实例处于何种状态。
  2. 当Promise实例调用then的时候,会立即创建一个新的Promise实例(thenPromise1)并返回。创建新的Promise时候,会在调用then的promise实例(promise1)中注册新的handler。
  3. 注册handler的时候,会判断promise实例(promise1)是否处于PENDING状态,如果是的话则将方法放入队列,否则直接执行对应状态的方法。

promise

0x04 完整代码

var PromiseA = (global => {
  const PENDING = 0;
  const FULFILLED = 1;
  const REJECTED = 2;
  let id = 0;
  function PromiseA(fn) {
    this.id = ++id;
    this.state = PENDING;
    this.handlers = [];
    fn.call(global, this._resolve.bind(this), this._reject.bind(this));
  }

  Object.assign(PromiseA.prototype, {
    then(onFulfilled, onRejected) {
      return new PromiseA((resolve, reject) => {
        this._registerHandler(
          result => {
            try {
              if (onFulfilled && onFulfilled instanceof Function) {
                resolve(onFulfilled(result));
              } else {
                resolve(result);
              }
            } catch (err) {
              reject(err);
            }
          },
          reason => {
            try {
              if (onRejected && onRejected instanceof Function) {
                resolve(onRejected(reason));
              } else {
                reject(reason);
              }
            } catch (err) {
              reject(err);
            }
          }
        );
      });
    },
    _resolve: function(result) {
      let then = result && this._getThen(result);
      if (then) {
        try {
          then.call(
            result,
            thenResult => {
              if (this.state !== PENDING) return;
              this._fulfill(thenResult);
            },
            thenReason => {
              if (this.state !== PENDING) return;
              this._reject(thenReason);
            }
          );
        } catch (e) {
          if (this.state !== PENDING) return;
          this._reject(e);
          debugger;
          console.error(e);
        }
      } else {
        this._fulfill(result);
      }
    },
    _reject(reason) {
      this._reject(reason);
    },
    _registerHandler(onFulfilled, onRejected) {
      if (this.state === PENDING) {
        this.handlers.push({
          onFulfilled,
          onRejected
        });
      } else {
        setTimeout(() => {
          if (this.state === FULFILLED) {
            onFulfilled(this.result);
          } else {
            onRejected(this.reason);
          }
        }, 0);
      }
    },
    _fulfill(result) {
      if (this.state !== PENDING) return;
      this.state = FULFILLED;
      this.result = result;
      this.handlers.forEach(handler => {
        debugger;
        handler.onFulfilled(result);
      });
    },
    _reject(reason) {
      if (this.state !== PENDING) return;
      this.state = REJECTED;
      this.reason = reason;
      this.handlers.forEach(handler => {
        handler.onRejected(reason);
      });
    },
    _getThen(result) {
      let then = result.then;
      if (then && then instanceof Function) {
        return then;
      }
      return null;
    }
  });

  return PromiseA;
})(this);

0x05 最后

PromiseA.resolve 的实现其实也很简单,各类扩展的方法就不展开讨论。最后简单的说一下PromiseA.resolve的函数怎么实现。

var PromiseA = (() => {

  function PromiseA(fn) {
    this.state = PENDING;
    this.handlers = [];
    fn(this._resolve.bind(this), this._reject.bind(this));
  }

  //...

  PromiseA.resolve = function(result){
    return new PromiseA(resolve=>resolve(result));
  }

  return PromiseA;
})();