手写基于A+规范的Promise
一、注释版
const $Promise = (function () {
function $Promise(executor) {
abnormalCheck.isFunction(executor);
createPromise(this);
let returnValue = executeFn(executor,result => {changePromise(this,'fulfilled',result)},result => {changePromise(this,'rejected',result)});
returnValue instanceof Error && changePromise(this,'rejected',returnValue);
}
/*$Promise
* 1、判断是否为函数,如果不是,直接报错
* 2、创建 promise,这里没有采用构造函数创建对象的方式,采用的是构造函数调用其他函数来创建对象的方式,这里的 this 就是 new 执行时产生的新对象,
* 这么做的原因是,创建 promise 的位置很多,对于有些地方,构造函数中有些代码时多余的,为了提升性能,也为了更好的复用,于是采用了这种设计
* 3、执行 executor函数,在执行时,还要检测是否会报错,所以使用了带有检测功能的 executeFn 函数来执行 executor 函数,并且还需要传递两个函数给 executor 函数,
* 调用这两个函数,将改变 promise 的状态,这里对原有 changePromise 函数进行了封装,目的是提前传参给它,并且要求这个函数是无返回的,所以在函数体加上了{},
* 这里也可以用 bind 替换箭头函数,但性能会有所下降
* 4、通过 executeFn 函数的返回值,判断是否在执行 executor 函数时存在异常,如果存在异常,更改 promise 的状态
* */
$Promise.prototype = {
constructor: $Promise,
/*原本原型对象会随函数一起创建,并且还有一个默认属性 constructor,由于这里将函数 prototype 重新指向了一个新对象,为了保证新原型对象的完整性,添加了这条语句*/
then (onResolved,onRejected) {
let nextPromise = createPromise({},'pending');
this[propertyName.nextPromiseQueue].push({nextPromise,onResolved,onRejected});
return nextPromise;
},
/*then
* 1、创建一个 promise
* 2、将新对象和 onResolved,onRejected 保存到上一个 promise 中,为了状态改变调用
* 3、返回新的 promise
* 后续的工作交给 changePromise 函数
* */
catch (onRejected) {
return this.then(null,onRejected);
},
/*catch 方法可以简单实现为 then 的语法糖*/
finally (onFinally) {
return this.then(result => {
onFinally();
return result;
},error => {
onFinally();
throw error;
});
}
/*finally
* finally 方法也可以通过 then 方法实现,给then传递两个函数,两个函数中都执行了 onFinally,保证 onFinally 一定会执行,并通过返回值,
* 实现了返回和上次类似的 promise
* 这里有一个注意点,一般情况来说,返回的 promise 和原来的 promise 长相相同,
* 但如果在执行函数时发生错误,则会返回 promise 的状态将是自身的错误
* 基于上面的特殊情况,进行了处理,这里的语句 throw error 一般情况下,将抛出上个 promise 的错误,让新的 promise 去接收,去更改状态,
* 由于在执行任何用户输入的函数,都利用了带有检测功能的 executeFn 函数来执行,所以如果函数自身有错,会优先使用自己的错误信息
* */
}
$Promise.resolve = function (result) {
return createPromise({},'fulfilled',result);
}
/*resolve 方法实际上就创建一个 promise*/
$Promise.reject = function (result) {
return createPromise({},'rejected',result);
}
/*reject 方法跟 resolve 方法相同*/
$Promise.all = function (promiseArr) {
let nextPromise = createPromise({},'pending'),
resultArr = Array(promiseArr.length),
amount = 0;
promiseArr.forEach((promise,index) => {
if (promise instanceof $Promise && promise[propertyName.state] === 'pending') { //处理待更新的 promise
promise.then(result => {
resultArr[index] = result;
},error => {
changePromise(nextPromise,'rejected',error);
})
} else if (promise instanceof $Promise) { //处理更新过的 promise
resultArr[index] = promise[propertyName.state] === 'fulfilled' ?
promise[propertyName.result] : changePromise(nextPromise,'rejected',promise[propertyName.result]);
} else { //处理非 promise
resultArr[index] = promise;
}
++amount === promiseArr.length && changePromise(nextPromise,'fulfilled',resultArr);
})
return nextPromise;
}
/*all
* 1、创建一个 promise
* 2、根据所有传入 promise 的状态,更新新的 promise
* 3、返回这个 promise
* 重点是第二步,如何监听到 promise 队列中每一个状态变化?
* 方法是在每一个 promise 上加入一个 then,只要 promise 状态一变,就会执行then中的函数,
* 执行函数时,首先要将 result 插入到 resultArr 的指定位置,然后进行判断插入的数量 amount 是否等于 promise 队列的长度,如果相等证明全部运行完,
* 最后通过 changePromise 函数更改新 promise 状态
* 需要注意的是,all 接收的数组中,不止可以有待更新的 promise,还可能有更新过的 promise,或者非 promise,
* 于是在原本基础上加上了 if-else 语句,用来处理这两种特殊情况
* */
function dell(obj,state,result) {
let [nextState,nextResult] = state === 'rejected' && obj.onRejected || state === 'fulfilled' && obj.onResolved ?
(() => {
let returnValue = state === 'fulfilled' ? executeFn(obj.onResolved,result) : executeFn(obj.onRejected,result);
if (returnValue instanceof $Promise) {
return [returnValue[propertyName.state],returnValue[propertyName.result]];
} else if (returnValue instanceof Error) {
return ['rejected',returnValue];
} else {
return ['fulfilled',returnValue];
}
})() : [state,result];
/*
let returnValue,nextState,nextResult;
if (state === 'rejected' && obj.onRejected || state === 'fulfilled' && obj.onResolved) {
returnValue = state === 'fulfilled' ? executeFn(obj.onResolved,result) : executeFn(obj.onRejected,result);
if (returnValue instanceof $Promise) {
[nextState,nextResult] = [returnValue[propertyName.state],returnValue[propertyName.result]];
} else if (returnValue instanceof Error) {
[nextState,nextResult] = ['rejected',returnValue];
} else {
[nextState,nextResult] = ['fulfilled',returnValue];
}
} else {
[nextState,nextResult] = [state,result];
}*/
changePromise(obj.nextPromise,nextState,nextResult);
}
/*dell
* 核心处理函数,根据上一个的状态和 onResolved, onRejected,计算出下一个 promise 的状态,然后执行 changePromise 函数,让它做更改
* dell 和 changePromise 的关系就像是节点和树枝,一个节点可以有多个树枝,树枝的末尾可以有一个新的节点,changePromise 函数就相当于节点
* 这部分用了两种代码实现的,被注释的版本更好理解,没被注释的版本可读性比较差,但性能会更加优秀
* 消耗一定的资源,所以减少语句有助于性能的提升
* */
function executeFn(fn,...params) {
try {return fn(...params)}
catch (error){return error}
}
/*executeFn
* 执行函数的函数,所有用户传入的函数,都将使用这个函数执行
* 如果没报错,正常返回,如果报错了,返回错误,调用者都会判断这个返回是否是错误
* */
function createPromise (promise,state,result){
[promise[propertyName.state],promise[propertyName.result]] = [state || 'pending',result];
promise[propertyName.nextPromiseQueue] = [];
promise.__proto__ = $Promise.prototype;
return promise;
}
/*createPromise
* 只用来创建 promise 对象
* 1、在空对象上加入 promise 应该有的属性,state 和 result,还有一个用于保存下一个 promise 和 onResolved, onRejected 的数组
* 2、由于原本的空对象的 __proto__ 指向的是 Object.prototype,需要修改为 $Promise.prototype,保证原型链的完整
* 3、返回这个 promise,这里手动传入一个对象(一般是空对象)和给返回的原因是为了方便部分代码的编写,例如 resolve 和 reject
* */
function changePromise (promise,state,result){
if(promise[propertyName.state] === 'pending'){
[promise[propertyName.state],promise[propertyName.result]] = [state,result];
setTimeout(() => {
abnormalCheck.hasRejectedDeal(promise);
promise[propertyName.nextPromiseQueue].forEach(obj => dell(obj,state,result))
},0);
}
}
/*changePromise
* 通过这个函数更新 promise
* 通过传入的参数,决定 promise 的最终状态,然后需要执行后续的 then 方法中的 onResolved, onRejected,通过函数的执行来更新下一个 promise
* 原本 onResolved, onRejected 在执行代码时,不会立即执行,而是放在微任务队列,等待宏任务执行结束,再去执行
* 因为无法实现微任务,所以采用了宏任务代替微任务
* 宏任务模拟微任务存在的问题:
* 微任务队列会一次执行完,而宏任务队列不会,每次只能执行一个宏任务,然后执行微任务队列和渲染页面,所以会存在时间上的误差
* 注意点:这里的 0s 后执行,如果不考虑硬件和操作系统的因素,理论上是可以做到的,根据HTML5标准,经过5重嵌套定时器之后,时间间隔被强制设定为至少4毫秒,
* 这里没有采用嵌套,所以它也可以立即执行
* 通过 abnormalCheck.hasRejectedDeal 方法可以检测是否有后续的 promise,如果没有并且当前 promise 还为 'rejected',将会报错
* 这样就实现了一条链上多个'rejected',最终只抛出一个错误的效果
* 最后就是遍历缓存的 promise 队列,通知他们需要执行 onResolved, onRejected,通过 dell 函数
* */
const propertyName = {
state: Symbol('State'),
result: Symbol('Result'),
nextPromiseQueue: Symbol('NextPromiseQueue')
}
/*为了防止 promise 属性不小心被修改,使用了局部 Symbol 作为属性名,这只能防止不小心,如果真想修改,还是有办法的*/
const abnormalCheck = {
isFunction (fn){ //判断是否是函数
if (typeof fn !== "function") {
let error = new Error("Promise resolver undefined is not a function");
error.name = "TypeError";
throw error;
}
},
hasRejectedDeal (promise){ //如果一个状态为 rejected 的 promise,并且后续没有后续 promise,将会报错
promise[propertyName.state] === 'rejected'
&& !promise[propertyName.nextPromiseQueue].length
&& console.error("Uncaught (in promise) " + promise[propertyName.result]);
}
}
/*abnormalCheck 异常检测对象,为了更好的复用,所以单独提取*/
return $Promise;
/*使用闭包进行返回,保证局部不会污染全局*/
})()
//export default $Promise //提供ES6导出
二、无注释版
const $Promise = (function () {
function $Promise(executor) {
abnormalCheck.isFunction(executor);
createPromise(this);
let returnValue = executeFn(executor,result => {changePromise(this,'fulfilled',result)},result => {changePromise(this,'rejected',result)});
returnValue instanceof Error && changePromise(this,'rejected',returnValue);
}
$Promise.prototype = {
constructor: $Promise,
then (onResolved,onRejected) {
let nextPromise = createPromise({},'pending');
this[propertyName.nextPromiseQueue].push({nextPromise,onResolved,onRejected});
return nextPromise;
},
catch (onRejected) {
return this.then(null,onRejected);
},
finally (onFinally) {
return this.then(result => {
onFinally();
return result;
},error => {
onFinally();
throw error;
});
}
}
$Promise.resolve = function (result) {
return createPromise({},'fulfilled',result);
}
$Promise.reject = function (result) {
return createPromise({},'rejected',result);
}
$Promise.all = function (promiseArr) {
let nextPromise = createPromise({},'pending'),
resultArr = Array(promiseArr.length),
amount = 0;
promiseArr.forEach((promise,index) => {
if (promise instanceof $Promise && promise[propertyName.state] === 'pending') {
promise.then(result => {
resultArr[index] = result;
},error => {
changePromise(nextPromise,'rejected',error);
})
} else if (promise instanceof $Promise) {
resultArr[index] = promise[propertyName.state] === 'fulfilled' ?
promise[propertyName.result] : changePromise(nextPromise,'rejected',promise[propertyName.result]);
} else {
resultArr[index] = promise;
}
++amount === promiseArr.length && changePromise(nextPromise,'fulfilled',resultArr);
})
return nextPromise;
}
function dell(obj,state,result) {
let [nextState,nextResult] = state === 'rejected' && obj.onRejected || state === 'fulfilled' && obj.onResolved ?
(() => {
let returnValue = state === 'fulfilled' ? executeFn(obj.onResolved,result) : executeFn(obj.onRejected,result);
if (returnValue instanceof $Promise) {
return [returnValue[propertyName.state],returnValue[propertyName.result]];
} else if (returnValue instanceof Error) {
return ['rejected',returnValue];
} else {
return ['fulfilled',returnValue];
}
})() : [state,result];
changePromise(obj.nextPromise,nextState,nextResult);
}
function executeFn(fn,...params) {
try {return fn(...params)}
catch (error){return error}
}
function createPromise (promise,state,result){
[promise[propertyName.state],promise[propertyName.result]] = [state || 'pending',result];
promise[propertyName.nextPromiseQueue] = [];
promise.__proto__ = $Promise.prototype;
return promise;
}
function changePromise (promise,state,result){
if(promise[propertyName.state] === 'pending'){
[promise[propertyName.state],promise[propertyName.result]] = [state,result];
setTimeout(() => {
abnormalCheck.hasRejectedDeal(promise);
promise[propertyName.nextPromiseQueue].forEach(obj => dell(obj,state,result))
},0);
}
}
const propertyName = {
state: Symbol('State'),
result: Symbol('Result'),
nextPromiseQueue: Symbol('NextPromiseQueue')
}
const abnormalCheck = {
isFunction (fn){
if (typeof fn !== "function") {
let error = new Error("Promise resolver undefined is not a function");
error.name = "TypeError";
throw error;
}
},
hasRejectedDeal (promise){
promise[propertyName.state] === 'rejected'
&& !promise[propertyName.nextPromiseQueue].length
&& console.error("Uncaught (in promise) " + promise[propertyName.result]);
}
}
return $Promise;
})()
//export default $Promise