自己动手写Promise

自己动手写Promise

相信作为一名JSer,大家如今肯定都对Promise的使用非常熟悉了。Promise的出现,大大改善了js代码『回调地狱』的问题,再结合async/await等语法特性,可以让JS书写简洁优美、可读性高的异步代码。

在Promise规范化的路上,社区的贡献可谓至关重要。早期各种版本的Promise库最终推动了Promises/A+规范的达成,并最终被纳入语言规范。如今随着async/await的出现,使得JS中的异步编程变得更加优雅、简洁。

今天我准备和大家一起,尝试自己动手实现一个简略版本的Promise的polyfill。

自己动手写Promise

Promise构造函数与状态

我们的Promise将处于以下三种状态中的一种:

  • PENDING:待定状态,也是Promise的初始状态
  • FULFILLED:已满足,即该Promise已被resolve
  • REJECTED:已拒绝,即该Promise已被reject

PENDING状态可以转换为FULFILLED或REJECTED状态,而后两者不能再次转换。

const STATUS = {
  PENDING: Symbol('PENDING'),
  FULFILLED: Symbol('FULFILLED'),
  REJECTED: Symbol('REJECTED'),
}
复制代码

在Promise构造函数中,我们会初始化一些属性:将状态置为PENDING,初始化回调数组。

我们将接收一个函数作为参数executor,随后这个函数将立即被调用,同时传入两个函数作为参数,调用这两个函数将分别resolve和reject当前promise:

class Promise {
  constructor(executor) {
    this.status = STATUS.PENDING;
    this.handlers = [];
    this._resolveFromExecutor(executor);
  }
}
复制代码

执行executor

接着我们执行executor,要注意的是执行时我们要使用try/catch,如果发生异常,则使用抛出的异常reject当前promise。executor也可以主动resolve或reject当前promise:

_resolveFromExecutor(executor) {
  const r = this._execute(executor, (value) => {
    this._resolveCallback(value);
  }, (reason) => {
    this._rejectCallback(reason);
  });

  if (r !== undefined) {
      this._rejectCallback(r);
  }
}
复制代码

resolve与reject

_resolveCallback与_rejectCallback都需要率先判断当前promise的状态是否为PENDING:若状态非PENDING则直接忽视调用;否则设置状态为FULFILLED或REJECTED,并且将值或拒绝原因记录下来,同时异步处理回调:

_resolveCallback(value) {
  if (this.status !== STATUS.PENDING) return;
  return this._fulfill(value);
}

_fulfill(value) {
  if (this.status !== STATUS.PENDING) return;
  this.status = STATUS.FULFILLED;
  this._value = value;

  async.settlePromises(this);
}

_rejectCallback(reason) {
  this._reject(reason);
}

_reject(reason) {
  if (this.status !== STATUS.PENDING) return;
  this.status = STATUS.REJECTED;
  this._reason = reason;

  async.settlePromises(this);
}
复制代码

异步处理

这里的async.settlePromises会异步调用promise._settlePromises。

js有许多异步执行的方式,包括简单的setTimeout、requestAnimationFrame,node环境下的nextTick、setImmediate,还有一些方法比如利用图片加载error或是MutationObserver等等。这里偷个懒直接用setTimeout了。

const async = {
  schedule(fn) {
    setTimeout(fn, 0);
  },
  settlePromises(promise) {
    this.schedule(() => {
      promise._settlePromises();
    });
  },
};
复制代码

_settlePromises将逐个执行handlers数组中注册的回调,并在此后清空handlers数组。在此实现_settlePromises方法之前,先来看看是如何向handlers数组添加回调的。

then与catch

then与cacth将调用_addCallbacks向handlers数组添加回调:

_addCallbacks(fulfill, reject, promise) {
  this.handlers.push({
    fulfill,
    reject,
    promise,
  });
}
复制代码

而then与catch是对私有方法_then的进一步包装:

then(didFulfill, didReject) {
  return this._then(didFulfill, didReject);
}

catch(fn) {
  return this.then(undefined, fn);
}
复制代码

每当调用_then方法将生成一个新的promise实例并返回:

_then(didFulfill, didReject) {
  const promise = new Promise(INTERNAL);
  let handler;
  let value;

  this._addCallbacks(didFulfill, didReject, promise);

  return promise;
}
复制代码

这里我们传入的executor将不会调用resolve或reject改变promise状态,而是将其加入父级promise的handlers数组并在父级_settlePromises时处理,由此形成了promise链:

parentPromise.then -> 生成childPromise并返回 -> 加入parentPromise的handlers

parentPromise._settlePromises -> 执行childPromise的_fulfill或_reject
复制代码

_settlePromises

_settlePromises会遍历handlers并调用_settlePromise。如果_then加入了回调函数,那我们需要调用这个函数并根据其结果去resolve或reject目标promise;否则直接用原本的结果来resolve或reject目标promise:

_settlePromises() {
  this.handlers.forEach(({ fulfill, reject, promise }) => {
    if (this.status === STATUS.FULFILLED) {
      this._settlePromise(promise, fulfill, this._value);
    } else {
      this._settlePromise(promise, reject, this._reason);
    }
  });
  this.handlers.length = 0;
}

_settlePromise(promise, handler, value) {
  if (typeof handler === 'function') {
      this._settlePromiseFromHandler(handler, value, promise);
  } else {
    if (promise.status === STATUS.FULFILLED) {
      promise._fulfill(value);
    } else {
      promise._reject(value);
    }
  }
}

_settlePromiseFromHandler(handler, value, promise) {
  const x = tryCatch(handler).call(null, value);

  if (x === errorObj) {
    promise._reject(x.e);
  } else {
    promise._resolveCallback(x);
  }
}
复制代码

Promise.resolve与Promise.reject

接着添加两个静态方法,返回一个promise示例,并立刻用传入的值resolve或reject这个promise。

Promise.resolve = function resolve(v) {
  return new Promise((res) => {
    res(v);
  });
};

Promise.reject = function reject(v) {
  return new Promise((_, rej) => {
    rej(v);
  });
};
复制代码

立即执行回调

当然,以上的代码并不会正确运行。

首先我们来看一下_then方法。我们需要判断当前promise是否是PENDING状态:如果是则将回调加入handlers数组;否则立即执行回调:

const async = {

  ...

  invoke(fn, receiver, arg) {
    this.schedule(() => {
      fn.call(receiver, arg);
    });
  },
};

_then(didFulfill, didReject) {
  const promise = new Promise(INTERNAL);
  const target = this;
  let handler;
  let value;

  if (target.status !== STATUS.PENDING) {
    if (target.status === STATUS.FULFILLED) {
      handler = didFulfill;
      value = target._value;
    } else if (target.status === STATUS.REJECTED) {
      handler = didReject;
      value = target._reason;
    }

    async.invoke(
      function ({ promise, handler, value }) {
        this._settlePromise(promise, handler, value);
      },
      target,
      {
        handler,
        promise,
        value,
      }
    );
  } else {
    target._addCallbacks(didFulfill, didReject, promise);
  }

  return promise;
}
复制代码

当resolve的值为promise实例

接下来还有一个问题要处理,如果一个promise被另一个promise所resolve,则需要进行特别的处理。

如果作为值的promise已经非PENDING状态,那比较简单,直接用它的结果resolve或reject当前的promise即可。如果目标promise还在PENDING状态,则将当前的promise以及它的handlers转交给目标promise。因为当前的promise可能也被作为其他promise的resolve的值,因此这里也要维护一个上级状态,以便找到链的最前端:

_resolveCallback(value) {
  if (this.status !== STATUS.PENDING) return;
  if (!(value instanceof Promise)) return this._fulfill(value);

  const p = value._target();

  if (p.status === STATUS.PENDING) {
    const len = this.handlers.length;
    this.handlers.forEach(({ fulfill, reject, promise }) => {
      p._addCallbacks(fulfill, reject, promise);
    });
    this._isFollowing = true;
    this.handlers.length = 0;
    this._followee = p;
  } else if (p.status === STATUS.FULFILLED) {
    this._fulfill(p._value);
  } else if (p.status === STATUS.REJECTED) {
    this._reject(p._reason);
  }
}

_target() {
  let ret = this;
  while (ret._isFollowing) ret = ret._followee;
  return ret;
}
复制代码

同时当我们调用promise._then时,也需要使用这个追溯机制:

_then(didFulfill, didReject) {
  const promise = new Promise(INTERNAL);
  const target = this;
  
  ...

}
复制代码

Promise.all

最后我们实现一下Promise.all。这里的思路很简单,生成一个promise示例,对传入的数组中的所有promise用then监听结果,如果全部resolve则用所有结果组成的数组resolve返回的promise,有一个失败则立即用这个错误reject:

class PromiseArray {
  constructor(values, count, isAll) {
    this._ps = values;
    this._count = isAll ? values.length : count;
    this._isAll = isAll;
    this._values = [];
    this._valueCount = 0;
    this._reasons = [];
    this._reasonCount = 0;
    this._promise = new Promise(INTERNAL);
    this._iterate();
  }

  _iterate() {
    let p;
    for (let i = 0; i < this._ps.length; i++) {
      p = this._ps[i];
      p.then(function (index, value) {
        if (this._isAll) {
          this._values[index] = value;
        } else {
          this._values.push(value);
        }
        this._valueCount++;
        this._check();
      }.bind(this, i), function (index, reason) {
        if (this._isAll) {
          this._reasons[index] = reason;
        } else {
          this._reasons.push(reason);
        }
        this._reasonCount++;
        this._check();
      }.bind(this, i));
    }
  }

  _check() {
    if (this._count <= this._valueCount) {
      this._promise._fulfill(this._values);
    } else if (this._ps.length - this._count < this._reasonCount) {
      this._promise._reject(this._reasons);
    }
  }
}

Promise.all = function (values) {
  return new PromiseArray(values, undefined, true)._promise;
};
复制代码

小结

实现Promise的关键点在于如何实现Promise链。

使用Promise以及async/await将大大提高代码的可读性、降低复杂度。

完整代码

(function () {
  const errorObj = {};
  let tryCatchTarget;

  const tryCatcher = function tryCatcher() {
    try {
      const target = tryCatchTarget;
      tryCatchTarget = null;
      return target.apply(this, arguments);
    } catch (e) {
      errorObj.e = e;
      return errorObj;
    }
  };
  const tryCatch = function tryCatch(fn) {
    tryCatchTarget = fn;
    return tryCatcher;
  };

  const async = {
    schedule(fn) {
      setTimeout(fn, 0);
    },
    invoke(fn, receiver, arg) {
      this.schedule(() => {
        fn.call(receiver, arg);
      });
    },
    settlePromises(promise) {
      this.schedule(() => {
        promise._settlePromises();
      });
    },
  };

  const INTERNAL = function INTERNAL() {};

  const STATUS = {
    PENDING: Symbol('PENDING'),
    FULFILLED: Symbol('FULFILLED'),
    REJECTED: Symbol('REJECTED'),
  }

  class Promise {
    constructor(executor) {
      this.status = STATUS.PENDING;
      this.handlers = [];
      this._isFollowing = false;
      this._followee = null;
      this._resolveFromExecutor(executor);
    }

    _resolveFromExecutor(executor) {
      // if (executor === INTERNAL) return;
      const r = this._execute(executor, (value) => {
        this._resolveCallback(value);
      }, (reason) => {
        this._rejectCallback(reason);
      });

      if (r !== undefined) {
          this._rejectCallback(r);
      }
    }

    _execute(executor, resolve, reject) {
      try {
        executor(resolve, reject);
      } catch (e) {
        return e;
      }
    }

    _resolveCallback(value) {
      if (this.status !== STATUS.PENDING) return;
      if (!(value instanceof Promise)) return this._fulfill(value);

      const p = value._target();

      if (p.status === STATUS.PENDING) {
        const len = this.handlers.length;
        this.handlers.forEach(({ fulfill, reject, promise }) => {
          p._addCallbacks(fulfill, reject, promise);
        });
        this._isFollowing = true;
        this.handlers.length = 0;
        this._followee = p;
      } else if (p.status === STATUS.FULFILLED) {
        this._fulfill(p._value);
      } else if (p.status === STATUS.REJECTED) {
        this._reject(p._reason);
      }
    }

    _target() {
      let ret = this;
      while (ret._isFollowing) ret = ret._followee;
      return ret;
    }

    _fulfill(value) {
      if (this.status !== STATUS.PENDING) return;
      this.status = STATUS.FULFILLED;
      this._value = value;

      async.settlePromises(this);
    }

    _rejectCallback(reason) {
      this._reject(reason);
    }

    _reject(reason) {
      if (this.status !== STATUS.PENDING) return;
      this.status = STATUS.REJECTED;
      this._reason = reason;

      async.settlePromises(this);
    }

    then(didFulfill, didReject) {
      return this._then(didFulfill, didReject);
    }

    _then(didFulfill, didReject) {
      const promise = new Promise(INTERNAL);
      const target = this._target();
      let handler;
      let value;

      if (target.status !== STATUS.PENDING) {
        if (target.status === STATUS.FULFILLED) {
          handler = didFulfill;
          value = target._value;
        } else if (target.status === STATUS.REJECTED) {
          handler = didReject;
          value = target._reason;
        }

        async.invoke(
          function ({ promise, handler, value }) {
            this._settlePromise(promise, handler, value);
          },
          target,
          {
            handler,
            promise,
            value,
          }
        );
      } else {
        target._addCallbacks(didFulfill, didReject, promise);
      }

      return promise;
    }

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

    _addCallbacks(fulfill, reject, promise) {
      this.handlers.push({
        fulfill,
        reject,
        promise,
      });
    }

    _settlePromises() {
      this.handlers.forEach(({ fulfill, reject, promise }) => {
        if (this.status === STATUS.FULFILLED) {
          this._settlePromise(promise, fulfill, this._value);
        } else {
          this._settlePromise(promise, reject, this._reason);
        }
      });
      this.handlers.length = 0;
    }

    _settlePromise(promise, handler, value) {
      if (typeof handler === 'function') {
          this._settlePromiseFromHandler(handler, value, promise);
      } else {
        if (promise.status === STATUS.FULFILLED) {
          promise._fulfill(value);
        } else {
          promise._reject(value);
        }
      }
    }

    _settlePromiseFromHandler(handler, value, promise) {
      const x = tryCatch(handler).call(null, value);

      if (x === errorObj) {
        promise._reject(x.e);
      } else {
        promise._resolveCallback(x);
      }
    }
  }

  Promise.resolve = function resolve(v) {
    return new Promise((res) => {
      res(v);
    });
  };
  Promise.reject = function reject(v) {
    return new Promise((_, rej) => {
      rej(v);
    });
  };

  window.Promise = Promise;

  class PromiseArray {
    constructor(values, count, isAll) {
      this._ps = values;
      this._count = isAll ? values.length : count;
      this._isAll = isAll;
      this._values = [];
      this._valueCount = 0;
      this._reasons = [];
      this._reasonCount = 0;
      this._promise = new Promise(INTERNAL);
      this._iterate();
    }

    _iterate() {
      let p;
      for (let i = 0; i < this._ps.length; i++) {
        p = this._ps[i];
        p.then(function (index, value) {
          if (this._isAll) {
            this._values[index] = value;
          } else {
            this._values.push(value);
          }
          this._valueCount++;
          this._check();
        }.bind(this, i), function (index, reason) {
          if (this._isAll) {
            this._reasons[index] = reason;
          } else {
            this._reasons.push(reason);
          }
          this._reasonCount++;
          this._check();
        }.bind(this, i));
      }
    }

    _check() {
      if (this._count <= this._valueCount) {
        this._promise._fulfill(this._values);
      } else if (this._ps.length - this._count < this._reasonCount) {
        this._promise._reject(this._reasons);
      }
    }
  }

  Promise.all = function (values) {
    return new PromiseArray(values, undefined, true)._promise;
  };
  Promise.some = function (values, count) {
    return new PromiseArray(values, count, false)._promise;
  };
  Promise.any = function (values) {
    return new PromiseArray(values, 1, false)._promise;
  };
})();
复制代码

参考

Bluebird

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值