媒介
根据文档申明简朴地完成 ES6 Promise
的各个要领并不难,然则Promise
的一些特别需求完成起来并不简朴,我起首提出一些不好完成或许轻易疏忽的需求:
- 数据通报
- 回调绑定
- 将回调变成 microtask
- 完成 then/finally 返回的
pending
promise “追随”它们的回调返回的pending
promise - 完成 resolve 返回的 promise “追随”它的
thenable
对象参数
完成框架
在处置惩罚上述题现在,我们先完成一个框架。
起首,我的目标是完成一个Promise
插件,它包含:
- 组织函数:Promise
- 静态要领:resolve、reject、all 和 race
- 实例要领:then、catch 和 finally
- 私有函数:identity、thrower 和 isSettled 等
以下:
;(function() {
function Promise(executor) {
}
Object.defineProperties(Promise, {
resolve: {
value: resolve,
configurable: true,
writable: true
},
reject: {
value: reject,
configurable: true,
writable: true
},
race: {
value: race,
configurable: true,
writable: true
},
all: {
value: all,
configurable: true,
writable: true
}
});
Promise.prototype = {
constructor: Promise,
then: function(onFulfilled, onRejected) {
},
catch: function(onRejected) {
},
finally: function(onFinally) {
},
}
function identity(value) {
return value;
}
function thrower(reason) {
throw reason;
}
function isSettled(pro) {
return pro instanceof Promise ? pro.status === 'fulfilled' || pro.status === 'rejected' : false;
}
window.Promise = Promise;
})();
处置惩罚题目
接下来,我们处置惩罚各个题目。
数据通报
为了通报数据——回调函数须要用到的参数以及 promise 的状况,我们起首在组织函数Promise
中给新天生的对象增加status
、value
和reason
属性,并且在组织函数中实行 executor
函数:
function Promise(executor) {
var self = this;
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
typeof executor === 'function' ? executor.call(null,
function(value) {
self.value = value;
self.status = 'fulfilled';
},
function(reason) {
self.reason = reason;
self.status = 'rejected';
}) : false;
}
我们将 value、reason 和 status 保存在 Promise 对象中,如许,我们就能够在 Promise 对象的要领中经由历程this
(即 Promise 对象的援用)来访问这些数据,并将其用作回调函数的参数。
根据文档申明,为了完成链式挪用,Promise
的一切要领都邑返回一个 Promise 对象,而且除了Promise.resolve(peomiseObj) 这类状况外都是新天生的 Promise 对象。所以接下来我的大部分要领都邑返回一个新的 promise 对象。不天生新对象的惯例:
var a = Promise.resolve('a'),
b = Promise.resolve(a);
console.log(a === b) //true
回调绑定
接下来,我们要将then
、catch
和finally
中的回调要领绑定到Promise
对象的状况转变这个事宜上。
我想到的第一个事宜就是onchange
事宜,然则 promiseObj.status 属性上并没有change
事宜。然则,我立时想到每次设置accessor
属性的值时,就会挪用 accessor 属性的setter
要领。那末,我只要把status
属性设置为存取属性,然后在它的 setter 要领里触发绑定的回调函数就行啦!以下:
function Promise(executor) {
var self = this;
//存储状况的私有属性
this._status = 'pending';
this.value = undefined;
this.reason = undefined;
//this.events = new Events();
//存储状况的公然属性
Object.defineProperty(this, 'status', {
get: function() {
return self._status;
},
set: function(newValue) {
self._status = newValue;
//self.events.fireEvent('change');
},
configurable: true
});
typeof executor === 'function' ? executor.call(null,
function(value) {
self.value = value;
self.status = 'fulfilled';
},
function(reason) {
self.reason = reason;
self.status = 'rejected';
}) : false;
}
为了绑定回调函数,我运用了宣布定阅形式。在then
、catch
和finally
要领实行的时刻定阅事宜change
,将本身的回调函数绑定到change
事宜上,promiseObj.status 属性是宣布者,一旦它的值发作转变就宣布change
事宜,实行回调函数。
为了节约篇幅,不那末主要的宣布者Events
() 组织函数及其原型我就不贴代码了,文章末端我会给出源代码。
完成 microtask
then
、catch
和finally
要领的回调函数都是microtask
,当满足前提(promise 对象状况转变)时,这些回调会被放入microtask
行列。每当挪用栈中的macrotask
实行终了时,马上实行microtask
行列中一切的microtask
,如许一次事宜轮回就完毕了,js引擎守候下一次轮回。
要我完成microtask
我是做不到的,我就只能用macrotask
模仿一下microtask
了。
我用 setTimeout 宣布的macrotask
举行模仿:
Object.defineProperty(this, 'status', {
get: function() {
return self._status;
},
set: function(newValue) {
self._status = newValue;
setTimeout(() => {
self.events.fireEvent('change');
},
0);
},
configurable: true
});
完成函数
接下来,我们完成各个函数和要领。在晓得要领的参数和返回值后再完成要领若有神助,而完成历程中最难处置惩罚的就是 pending 状况的 promise 对象,因为我们要等它变成别的状况时,再做真正的处置惩罚。下面我拿出两个最具代表性的要领来剖析。
静态要领all
假如忘记了 Promise.all
(iterable) 的参数和返回值,能够返回我上一篇文章检察。
function all(iterable) {
//假如 iterable 不是一个可迭代对象
if (iterable[Symbol.iterator] == undefined) {
let err = new TypeError(typeof iterable + iterable + ' is not iterable (cannot read property Symbol(Symbol.iterator))');
return Promise.reject(err);
}
//假如 iterable 对象为空
if (iterable.length === 0) {
return Promise.resolve([]);
}
//别的状况用异步处置惩罚
var pro = new Promise(), //all 返回的 promise 对象
valueArr = []; //all 返回的 promise 对象的 value 属性
setTimeout(function() {
var index = 0, //纪录当前索引
count = 0,
len = iterable.length;
for (let val of iterable) { -
function(i) {
if (val instanceof Promise) { //当前值为 Promise 对象时
if (val.status === 'pending') {
val.then(function(value) {
valueArr[i] = value;
count++;
//Promise.all([new Promise(function(resolve){setTimeout(resolve, 100, 1)}), 2, 3, 4])
if (count === len) {
pro.value = valueArr;
pro.status = 'fulfilled';
}
},
function(reason) {
pro.reason = reason;
pro.status = 'rejected';
//当一个pending Promise起首完成时,消除别的 pending Promise的事宜,防备以后别的 Promise 转变 pro 的状况
for (let uselessPromise of iterable) {
if (uselessPromise instanceof Promise && uselessPromise.status === 'pending') {
uselessPromise.events.removeEvent('change');
}
}
});
} else if (val.status === 'rejected') {
pro.reason = val.reason;
pro.status = 'rejected';
return;
} else {
//val.status === 'fulfilled'
valueArr[i] = val.value;
count++;
}
} else {
valueArr[i] = val;
count++;
}
index++;
} (index);
}
//假如 iterable 对象中的 promise 对象都变成 fulfilled 状况,或许 iterable 对象内没有 promise 对象,
//因为我们能够须要守候 pending promise 的效果,所以要分外消费一个变量计数,而不能用valueArr的长度推断。
if (count === len) {
pro.value = valueArr;
pro.status = 'fulfilled';
}
},
0);
return pro;
}
这里诠释两点:
1、怎样保证 value 数组中值的递次
假如iterable对象中的 promise 对象都变成 fulfilled 状况,或许 iterable 对象内没有 promise 对象,all 返回一个 fulfilled promise 对象,且其 value 值为 iterable 中各项值构成的数组,数组中的值将会根据 iterable 内的递次排列,而不是由 pending promise 的完成递次决议。
为了保证 value 数组中值的递次,最简朴的要领是
valueArr[iterable.indexOf(val)] = val.value;
然则像除 Array、TypedArray 和 String 外的 Map 和 Set 原生 iterabe 对象,以及别的经由历程myIterable[Symbol.iterator] 建立的自定义的 iterable 对象都没有 indexOf 要领,所以我挑选用闭包来保证 value 数组值的递次。
2、处置惩罚 pending promise 对象。
pending promise 是致使这个函数要分外增加许多变量存储状况,分外做许多推断和处置惩罚的罪魁祸首。
假如 iterabe 对象中有一个pending
状况的 promise(一般为一个异步的 promise),我们就运用then
要领来延续关注它的动态。
- 一旦它变成
fulfilled
promise,就将它的 value 到场 valueArr 数组。我们增加一个 count 变量纪录现在 valueArr 猎取到了多少个值,当悉数猎取到值后,就能够给 pro.value 和pro.status 赋值了。之所以用 count 而不是 valueArr.length 推断,是因为 valueArr = [undefined,undefined,undefined,1] 的长度也为4,如许能够致使还没猎取到 pending promise 的值就转变 pro.status 了。 - 而当它变成
rejected
promise 时,我们就更新 all 要领返回的对象的 reason 值,同时转变状况 status 为 rejected,触发绑定的onrejected
函数。别的,为了与原生 Promise 表现雷同:假如 iterable 对象中恣意一个 pending promise 对象状况变成rejected
,将不再延续关注别的 pending promise 的动态。而我早就在一切的 pending promise 上都绑定了 onfulfilled 和 onrejected 函数,用来跟踪它们。所以我须要在某个 pending promise 变成 rejected promise 时,删除它们绑定的回调函数。
实例要领then
Promise.prototype.then
(onFulfilled, onRejected):
Promise.prototype.then = function(onFulfilled, onRejected) {
var pro = new Promise();
//绑定回调函数,onFulfilled 和 onRejected 用一个回调函数处置惩罚
this.events.addEvent('change', hander.bind(null, this));
function hander(that) {
var res; //onFulfilled 或 onRejected 回调函数实行后获得的效果
try {
if (that.status === 'fulfilled') {
//假如onFulfilled不是函数,它会在then要领内部被替换成一个 Identity 函数
typeof onFulfilled !== 'function' ? onFulfilled = identity: false;
//将参数 this.value 传入 onFulfilled 并实行,将效果赋给 res
res = onFulfilled.call(null, that.value);
} else if (that.status === 'rejected') {
//假如onRejected不是函数,它会在then要领内部被替换成一个 Thrower 函数
typeof onRejected !== 'function' ? onRejected = thrower: false;
res = onRejected.call(null, that.reason);
}
} catch(err) {
//抛出一个毛病,状况3
pro.reason = err;
pro.status = 'rejected';
return;
}
if (res instanceof Promise) {
if (res.status === 'fulfilled') { //状况4
pro.value = res.value;
pro.status = 'fulfilled';
} else if (res.status === 'rejected') { //状况5
pro.reason = res.reason;
pro.status = 'rejected';
} else { //状况6
//res.status === 'pending'时,pro 追随 res
pro.status = 'pending';
res.then(function(value) {
pro.value = value;
pro.status = 'fulfilled';
},
function(reason) {
pro.reason = reason;
pro.status = 'rejected';
});
}
} else {
//回调函数返回一个值或不返回任何内容,状况1、2
pro.value = res;
pro.status = 'fulfilled';
}
}
return pro;
};
我想我已解释得很清晰了,能够对比我上一篇文章举行浏览。
我再申明一下pending promise 的“追随”状况,和 all 要领的完成体式格局差不多,这里也是用 res.then
来“追随”的。我置信人人都看得懂代码,下面我举个例子来实践一下:
var fromCallback;
var fromThen = Promise.resolve('done')
.then(function onFulfilled(value) {
fromCallback = new Promise(function(resolve){
setTimeout(() => resolve(value), 0); //未实行 setTimeout 的回调要领之前 fromCallback 为'pending'状况
});
return fromCallback; //then 要领返回的 fromThen 将追随 onFulfilled 要领返回的 fromCallback
});
setTimeout(function() {
//现在已实行完 onFulfilled 回调函数,fromCallback 为'pending'状况,fromThen ‘追随’ fromCallback
console.log(fromCallback.status); //fromCallback.status === 'pending'
console.log(fromThen.status); //fromThen.status === 'pending'
setTimeout(function() {
//现在已实行完 setTimeout 中的回调函数,fromCallback 为'fulfilled'状况,fromThen 也随着变成'fulfilled'状况
console.log(fromCallback.status + ' ' + fromCallback.value); //fromCallback.status === 'fulfilled'
console.log(fromThen.status + ' ' + fromThen.value); //fromThen.status === 'fulfilled'
console.log(fromCallback === fromThen); //false
}, 10); //将这个 delay 参数改成 0 尝尝
}, 0);
看完这个例子,我置信人人都搞懂了then
的回调函数返回 pending promise 时它会怎样处置惩罚了。
别的,这个例子也体现出我用 setTimeout 分发的macrotask
模仿microtask
的不足之处了,假如将倒数第二行的的 delay 参数改成 0,那末 fromThen.status === ‘pending’,这申明修正它状况的代码在 log 它状况的代码以后实行,至于缘由人人本身想一下,这涉及到 event loop。
测试
列位大侠请点下面的链接举行测试:
https://codepen.io/lyl123321/…
或许直接点这里检察源代码:
https://github.com/lyl123321/…
新增 Promise.try:
https://github.com/lyl123321/…