本篇将分析最后的catch、all、race方法。
首先是catch方法,回想下catch方法的使用方式,我们一般将其放在Promise链的最后,用来捕获拒绝的原因。因此,catch方法也应该定义在Promise的原型链上,我们来看其实现:
Promise.prototype['catch'] = function(onRejected) {
return this.then(null, onRejected);
};
可以看到,catch方法就是一个低配版的then方法,只接收一个拒绝回调参数。因此,我们得出,catch方法并不是Promise链的终端,其后可以继续链式调用。
再来看race方法,race方法接收一个数组,当数组中任何一个Promise解决或拒绝,就返回一个解决或拒绝状态的Promise。其实现:
Promise.race = function(values) {
return new Promise(function(resolve, reject) {
for (var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
};
整个方法返回了实例化的Promise对象,在回调参数中遍历传入的数组,调用每个数组元素的then方法,并传入resolve、reject。总的来看,其实现相当简洁,将Promise的状态交给每个数组元素的then方法来决定,因为状态一旦决定就不会再改变,因此也不需要一个标记来中途退出循环。
最后来看下all方法,all方法也接收一个数组,当数组中所有Promise都完成,返回一个完成状态的Promise对象,当数组中任一Promise被拒绝,返回一个拒绝状态的Promise对象。其实现:
Promise.all = function(arr) {
return new Promise(function(resolve, reject) {
if (!arr || typeof arr.length === 'undefined')
throw new TypeError('Promise.all accepts an array');
var args = Array.prototype.slice.call(arr);
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(
val,
function(val) {
res(i, val);
},
reject
);
return;
}
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
reject(ex);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
整个方法也是返回了一个实例化的Promise对象,我们来看起回调参数:
if (!arr || typeof arr.length === 'undefined')
throw new TypeError('Promise.all accepts an array');
首先对传入的参数做了判断,若未传入参数或传入的参数没有length属性,则会抛出异常。也就是说,传入的参数可以不为数组,而是一个类数组对象!
接下来:
var args = Array.prototype.slice.call(arr);
将传入的参数转化为真正的数组,保存在args变量中。
if (args.length === 0) return resolve([]);
若传入的数组长度为0,则返回Promise对象的值为空数组。
var remaining = args.length;
将数组的长度保存在remaining变量中。
接下来定义了一个内部函数res,我们先看是如何调用的:
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
遍历数组,将数组下标与数组元素作为参数,调用res函数。来看看res函数具体做了什么:
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
...
}
args[i] = val;
if (--remaining === 0) {
resolve(args);
}
} catch (ex) {
reject(ex);
}
忽略第一个条件判断,将val赋值给与其下标对应的数组元素,也就将原来的值覆盖掉,这两个值难道不是原谅就是同一个吗?我们可以猜测,上一个一定对val值做了处理。
每次讲remaining变量的值自减1,如果最后值等于0,也就是遍历完成,调用resolve(args),由此可知,返回完成状态的Promise对象的值为一个数组,其数组元素为处理后传入的数组元素,并且可以知道,其数组元素的顺序并没有发生改变!
若抛出异常,则调用reject,将原因作为参数,与我们知道的一致,任意一个被拒绝,返回的Promise对象的值为单一的拒绝原因,而非数组!
再来看第一个条件判断,我们可以思考下,什么样的值才会需要做处理呢?我们调用all方法,最可能传入的值是不是Promise对象?当然还有thenable对象,由此可知,只有这些值才会被处理!
if (val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if (typeof then === 'function') {
then.call(
val,
function(val) {
res(i, val);
},
reject
);
return;
}
}
判断val是对象或函数类型,再判断其then方法是否是函数类型,这些判断在验证就是我们在第一篇中所说的thenable类型。若是thenable类型,则调用val的then方法,以val为this,完成回调为递归调用res函数,知道val不为thenable类型,最后结束此次调用。
不知道大家有没有疑问,反正我当时是有,resolve(args)调用的位置,为什么不是在这:
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
resolve(args);
难道不是在数组遍历完成再调用就可以了吗?其实要注意的是,res函数中调用了then方法,而then方法是异步执行的!所以要确保调用resolve(args)前,所有的Promise状态已经改变!
至此,Promise-Polyfill的源码就分析完毕了。
传送门:
Promise-Polyfill源码解析(1)
Promise-Polyfill源码解析(2)