紧接着上一节梳理完了resovle决策的流程和原理,现在就来梳理reject的流程和原理了。建议先看上一节,然后结合结合源码阅读。
reject 被调用的情景
我尝试在源码的页面去搜索reject,排除一些声明和一些原型链上的方法,也就是all、race这些等调用的场景,匹配到的还存在四个地方。分别是:
- 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的两种状况。
- 当我们调用
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
}
}
- 在状态已经变更了,准备去处理和调用
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;
}
首先会在下一个异步任务中判断当前的被reject的promise是否被处理过,不然就会调用_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。