promise-polyfill 梳理分析【二:reject决策】

715 阅读4分钟

紧接着上一节梳理完了resovle决策的流程和原理,现在就来梳理reject的流程和原理了。建议先看上一节,然后结合结合源码阅读。

第一节

reject 被调用的情景

我尝试在源码的页面去搜索reject,排除一些声明和一些原型链上的方法,也就是all、race这些等调用的场景,匹配到的还存在四个地方。分别是:

  1. doResolve 函数中,两处调用。

一处是,当我们去执行构造函数传入的fn函数,使用try-catch捕获到的fn函数执行错误。

第二处就是,在我们调用fn函数时,传入的reject函数被手动调用的时候。

function doResolve(fn, self) {
  var done = false;
  try {
    fn(
      function(value) {
        if (done) return;
        done = true;
        resolve(self, value);
      },
      function(reason) {
        if (done) return;
        done = true;
        reject(self, reason);  // reject 在 fn 函数内部被手动调用了。
      }
    );
  } catch (ex) {
    if (done) return;
    done = true;
    reject(self, ex);   // fn 函数直接执行出错了。
  }
}

以上两种情况,分别对应了我们使用promise的时候这两种情况:

const p = new Promise(function (resolve, reject) {
  setTimeout(() => {
    reject('就是想报错') // 第二种情况,手动调用 reject
  }, 4000)
  throw new Error("就是想报错") // 第一种情况,fn 函数执行的时候出错。
})

以上就是创建promise对象的时候可能导致reject的两种状况。

  1. 当我们调用resolve的时候,将状态从0-1或者0-3的时候,检查变量和执行finale函数的时候报错了就去调用reject
function resolve(self, newValue) {
  try {
    // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
    if (newValue === self)
      throw new TypeError('A promise cannot be resolved with itself.');
    if (
      newValue &&
      (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
      var then = newValue.then;
      if (newValue instanceof Promise) {
        self._state = 3;
        self._value = newValue;
        finale(self);
        return;
      } else if (typeof then === 'function') {
        doResolve(bind(then, newValue), self);
        return;
      }
    }
    self._state = 1;
    self._value = newValue;
    finale(self);
  } catch (e) {
    reject(self, e);  // 这么try-catch 这么大,检查了很多错误,只要resolve改变状态的过程中,出错了,就直接 调用 reject
  }
}
  1. 在状态已经变更了,准备去处理和调用then函数传入的handler后,出现了错误。也就是then传入的函数执行的时候出现了错误。
function handle(self, deferred) {
  while (self._state === 3) {
    self = self._value;
  }
  if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }
  self._handled = true;
  Promise._immediateFn(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
      return;
    }
    var ret;
    try {
      ret = cb(self._value);
    } catch (e) {
      reject(deferred.promise, e);  // 当 cb ,根据状态会拿到then传入的第一个函数还是第二个函数,然后去执行,如果报错了,就去调用 reject
      return;
    }
    resolve(deferred.promise, ret);
  });
}

上面这种状态对应这样的场景:

p.then(function (data) {
  throw new Error("我就是想报错")
}, function (err) {
  throw new Error("我就是想报错")
})

reject 函数的实现

reject 的函数实现非常简单,它不需要想resolve函数那样需要检查传入的值来修改对象的状态1或者3。它只能将状态修改成2,并且记录传入的错误原因就行。

function reject(self, newValue) {
  self._state = 2;
  self._value = newValue;
  finale(self);
}

finale 函数

又来到了这个函数,之前的resolve最终也会调用这函数来处理。我们看看

function finale(self) {
  if (self._state === 2 && self._deferreds.length === 0) {
    Promise._immediateFn(function() {
      if (!self._handled) {
        Promise._unhandledRejectionFn(self._value);
      }
    });
  }

  for (var i = 0, len = self._deferreds.length; i < len; i++) {
    handle(self, self._deferreds[i]);
  }
  self._deferreds = null;
}

首先会在下一个异步任务中判断当前的被rejectpromise是否被处理过,不然就会调用_unhandledRejectionFn在控制台报警告。然后在立即去遍历执行then传入的handler,可以看到handler的执行是会在判断是否被处理过之前执行的。

reject 的链式调用

我们一般会在promise的最后写上.then(...).catch((err) => {}),很显然,这里的预期是,如果,其中某一个then出现了错误或者被reject了,会将当前的错误一直传递到最后。那么这是怎么工作的呢?让我们先看向finale函数,然后在看向handle函数。

首先,上面说过执行了reject后,必定会调用finale函数,然后finale函数会去执行handler,所以我们看看hande函数里面是怎么处理handler的。

function handle(self, deferred) {
  ...
  self._handled = true;  // 首先标明,这个 promise 已经处理过了
  Promise._immediateFn(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
      return;
    }
    var ret;
    try {
      ret = cb(self._value);
    } catch (e) {
      reject(deferred.promise, e);
      return;
    }
    resolve(deferred.promise, ret);
  });
}

主要是看这里

var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
   (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
   return;
}

如果状态为 2 了,那么 cb 拿到的值就是then函数传入的二个参数onRejected,但是一般我很少会传入这个参数,除非有对错误的特殊处理,所以就有了第二地方的判断,cb === null,如果为空的会,默认将cb = reject 就是默认的reject函数。

到这里就清楚了,如果我们在构造函数里面被调用了reject之后,默认会调用then(onReoslve, onReject)里面的onReject,如果这个没有,就调用默认的reject来再次改变then创建的新的promise的状态为 2, 链式调用就以此往下转移状态了。

小结

reject的流程比resolve相对简单,经过梳理分析,我已经了解到reject可能被触发的几个场景,和触发了之后还会存在错误没有处理的警告等。

下一节来分析几个原型链上面的实现。all、rece和finally

原文地址