Promise-Polyfill源码解析(3)
本篇将分析最后的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的源码就分析完毕了。
作者:xshinei
链接:https://www.jianshu.com/p/7590e24f5b62
共同学习,写下你的评论
评论加载中...
作者其他优质文章