Promise可以说是JavaScript中异步宏任务的代表,要想真正理解的Promise就得从其源码角度,所以这里我们尝试用JavaScript来实现一个Promise类。
要求
根据Promise/A+原理,有以下几个点需要遵守:
-
Promise有一个Promise States,其有三种状态:pending:初始状态,可以转化为fulfilled,rejected。fulfilled:- 需要一个
value作为结果。 - 不能转化为其他状态。
- 需要一个
rejected:- 需要一个
reason作为一个原因。 - 不能转化为其他状态。
- 需要一个
-
必须要一个
then方法,接受两个参数onFulfilled:当操作成功时发生的回调。onRejected:当操作失败时发生的回调。
一旦
promise完成,每一次调用then方法,得到的结果必须是相同的。 -
Promise返回的必须也是一个promise。
原API兼容性
Promise
开发
实现1,2功能
首先我们实现第一个和第二个特点:
基本思路如下:
-
一个
Promise首先要有一个status,表示当前的状态,其有三种状态:PENDING:表示当前Promise未完成,此时的回调函数会加入相应的队列RESOLVED:表示当前Promise已经完成,且时resolve状态,直接调用成功的函数。REJECTED:表示当前Promise已经完成,且时rejected状态,直接调用失败的函数。
-
然后我们要定义两个值:
value:用于resolve时的返回值。reason:用于rejected时的返回值。
-
上面说到当
Promise状态为PENDING时,我们会把回调函数放到相应的队列,所以我们还会定义两个队列resolvedCallQueue:存放成功的回调函数rejectedCallQueue:存放失败的回调函数
-
然后我们定义
Promise的resolve和rejected函数,在这两个函数中,我们会改变Promise的状态并且给value或者reason赋值,最后执行相应的函数队列。 -
最后我们会定义
Promise的then方法。如同上面对于状态的定义,我们会根据当前Promise的状态来处理then方法传来的回调函数。
下面用ES6的class来实现:
class Promise {
constructor(executer) {
//定义Promise状态枚举数据
this.status_enum = {
PENDING: 'PENDING',
RESOLVED: 'RESOLVED',
REJECTED: 'REJECTED',
};
//成功的返回值
this.value = undefined;
//失败的返回值
this.reason = undefined;
//成功的回调函数队列
this.resolvedCallQueue = [];
//失败的回调函数队列
this.rejectedCallQueue = [];
//当前Promise状态
this.status = this.status_enum.PENDING;
//Promise完成
let resolve = (value) => {
if (this.status === this.status_enum.PENDING) {
this.status = this.status_enum.RESOLVED;
this.value = value;
for (let i = 0; i < this.resolvedCallQueue.length; i++) {
this.resolvedCallQueue[i](this.value);
}
}
};
//Promise失败
let reject = (reason) => {
if (this.status === this.status_enum.PENDING) {
this.status = this.status_enum.REJECTED;
this.reason = reason;
for (let i = 0; i < rejectedCallQueue.length; i++) {
this.rejectedCallQueue[i](this.reason);
}
}
};
//执行Promise函数
try {
executer(resolve, reject);
} catch (e) {
console.log('错误', e);
reject(e);
}
}
//定义then函数
then(onfulfilled, onrejected) {
//检测参数必须为函数
if(!onfulfilled instanceof Function || !onrejected instanceof Function){
throw new Error('Uncaught TypeError: Promise resolver is not a function')
}
//当Promise状态为RESOLVED时,进行成功回调函数
if (this.status === this.status_enum.RESOLVED) {
onfulfilled(this.value);
}
//当Promise状态为REJECTED时,进行失败回调函数
if (this.status === this.status_enum.REJECTED) {
onrejected(this.reason);
}
//当Promise状态为PENDING时,将其回调函数加入相应队列,在完成时会执行相应的函数队列
if (this.status === this.status_enum.PENDING) {
this.resolvedCallQueue.push(onfulfilled);
this.rejectedCallQueue.push(onrejected);
}
}
}
下面用原型实现(基本原理一样,因为class本质也是原型链的语法糖):
function Promise(executer) {
this.status_enum = {
PENDING: 'PENDING',
RESOLVED: 'RESOLVED',
REJECTED: 'REJECTED',
};
this.value = undefined;
this.reason = undefined;
this.resolvedCallQueue = [];
this.rejectedCallQueue = [];
this.status = this.status_enum.PENDING;
let resolve = (value) => {
if (this.status === this.status_enum.PENDING) {
this.status = this.status_enum.RESOLVED;
this.value = value;
for (let i = 0; i < this.resolvedCallQueue.length; i++) {
this.resolvedCallQueue[i](this.value);
}
}
};
let reject = (reason) => {
if (this.status === this.status_enum.PENDING) {
this.status = this.status_enum.REJECTED;
this.reason = reason;
for (let i = 0; i < rejectedCallQueue.length; i++) {
this.rejectedCallQueue[i](this.reason);
}
}
};
try {
executer(resolve, reject);
} catch (e) {
console.log('错误', e);
reject(e);
}
}
Promise2.prototype.then = function then(onfulfilled, onrejected) {
if (this.status === this.status_enum.RESOLVED) {
onfulfilled(this.value);
}
if (this.status === this.status_enum.REJECTED) {
onrejected(this.reason);
}
if (this.status === this.status_enum.PENDING) {
this.resolvedCallQueue.push(onfulfilled);
this.rejectedCallQueue.push(onrejected);
}
};
这样,Promise的基本功能就已经实现,简单测试如下:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2秒!!!');
resolve(111);
}, 2000);
});
p.then((res) => {
console.log('p1');
console.log(res);
});
p.then((res) => {
console.log('p2');
console.log(res);
});
结果:
2秒!!!
p1
111
p2
111
实现1,2,3功能
前面我们基本实现了Promise的基本功能,但是还是一个问题是:Promise必须返回一个Promise,上面的代码并不满足这一点。
所谓我们为了实现返回的都是Promise,我们需要重写then方法,使之返回的是一个Promise,这时,我们还是要根据当前Promise的状态来分开来处理。
但是其中心点是,必须持续执行Promise,直到其返回值不是一个thenable对象或方法,所以这是一个递归的过程。所以我们定义了一个函数cycleResolve,其接受四个参数:
newPromise:新建的被用于返回的Promisetarget:回调函数执行的获得结果(可能仍然是一个Promise)resolve:新Promise的resolve函数reject:新Promise的reject函数
该函数的功能是:判定返回值target是不是一个thenable对象(包括Promise),如果是,继续执行其then方法。经过这个过程,知道返回值不是thenable对象,然后将其值resolve出去。
注意:
- 只有
target是一个thenable对象并且其then属性是一个函数时,才会调用其then方法,否则会直接将targetresolve出去。 - 一旦遇到错误,都会直接
reject(e) then方法中必须使用setTimeout来使内部的操作成为宏任务,在下一个tick执行。这样才能拿到newPromise,否则会出现未初始化错误。
class Promise {
constructor(executer) {
this.status_enum = {
PENDING: 'PENDING',
RESOLVED: 'RESOLVED',
REJECTED: 'REJECTED',
};
this.value = undefined;
this.reason = undefined;
this.resolvedCallQueue = [];
this.rejectedCallQueue = [];
this.status = this.status_enum.PENDING;
//定义resolve方法,任务完成时调度
this.resolve = (value) => {
if (this.status === this.status_enum.PENDING) {
this.status = this.status_enum.RESOLVED;
this.value = value;
for (let i = 0; i < this.resolvedCallQueue.length; i++) {
this.resolvedCallQueue[i](this.value);
}
}
};
//定义reject方法,任务失败时调度
this.reject = (reason) => {
if (this.status === this.status_enum.PENDING) {
this.status = this.status_enum.REJECTED;
this.reason = reason;
for (let i = 0; i < this.rejectedCallQueue.length; i++) {
this.rejectedCallQueue[i](this.reason);
}
}
};
//执行定义Promise时传入的任务
try {
executer(this.resolve, this.reject);
} catch (e) {
console.log('错误', e);
this.reject(e);
}
}
//定义then方法
then(onfulfilled, onrejected) {
//新建一个Promise用于返回
let newPromise = new Promise((resolve, reject) => {
if (this.status === this.status_enum.RESOLVED) {
setTimeout(() => {
try {
let target = onfulfilled(this.value);
cycleResolve(newPromise, target, resolve, reject);
} catch (e) {
console.log(e);
reject(e);
}
}, 0);
}
if (this.status === this.status_enum.REJECTED) {
//建立宏任务,方便拿到newPromise,否则会出现未初始化错误
setTimeout(() => {
try {
let target = onrejected(this.reason);
cycleResolve(newPromise, target, resolve, reject);
} catch (e) {
console.log(e);
reject(e);
}
});
}
if (this.status === this.status_enum.PENDING) {
this.resolvedCallQueue.push(() => {
setTimeout(() => {
try {
let target = onfulfilled(this.value); //将resolve函数保留的成功值传递作为参数
cycleResolve(newPromise, target, resolve, reject);
} catch (e) {
console.log(e);
reject(e);
}
}, 0);
});
this.rejectedCallQueue.push(() => {
setTimeout(() => {
try {
let target = onrejected(this.reason); //将resolve函数保留的成功值传递作为参数
cycleResolve(newPromise, target, resolve, reject);
} catch (e) {
console.log(e);
reject(e);
}
}, 0);
});
}
});
return newPromise;
}
function cycleResolve(newPromise, target, resolve, reject) {
//禁止循环调用
if (newPromise === target) {
return reject(new TypeError('循环调用'));
}
if (
target !== null &&
(typeof target === 'object' ||
typeof target === 'function') /*确定其是一个对象*/
) {
try {
let then = target.then; /*确定是否是一个thenable对象*/
if (typeof then === 'function') {
then.call(
target,
(newTarget) => {
resolvePromise(newPromise, newTarget, resolve, reject);
},
(e) => {
reject(e);
}
);
} else {
resolve(target);
}
} catch (e) {
reject(e);
}
} else {
resolve(target);
}
}
至此,Promise的基本功能都已经完成,接下来我们完成剩下的一些细枝末节的东西,包括:
- 实现
resolve方法 - 实现
reject方法 - 实现
catch方法。 - 实现
finally方法。 - 实现
Promise的其他方法:Promise.all([p1, p2, p3])Promise.race([p1, p2, p3])Promise.allSettled(p1, p2, p3)Promise.any(p1, p2, p3)
注意:
- 各个函数的功能不再赘述,具体查看Promise理解
- 下面的代码就只写对应部分,多余部分不再进行赘述。
实现catch方法
catch(onrejected) { /*在class中定义catch方法*/
const newPromise = new Promise3((undefined, reject) => {
if (this.status === this.status_enum.REJECTED) {
setTimeout(() => {
try {
let target = onrejected(this.reason);
cycleReject(newPromise, target, reject);
} catch (e) {
console.log(e)
reject(e);
}
}, 0);
}
if (this.status === this.status_enum.PENDING) {
this.rejectedCallQueue.push(() => {
setTimeout(() => {
try {
let target = onrejected(this.reason);
cycleReject(newPromise, target, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
}
}
//循环reject
function cycleReject(newPromise, target, reject) {
//禁止循环调用
if (newPromise === target) {
return reject(new TypeError('循环调用'));
}
if (
target !== null &&
(typeof target === 'object' ||
typeof target === 'function') /*确定其是一个对象*/
) {
try {
let then = target.then; /*确定是否是一个thenable对象*/
if (typeof then === 'function') {
then.call(
target,
(newTarget) => {
resolvePromise(newPromise, newTarget, reject);
},
(e) => {
reject(e);
}
);
}
} catch (e) {
reject(e);
}
}else{
reject(target)
}
}
其实这里的操作与then方法逻辑基本一致,唯一不同的是,我们只需要捕捉reject,不捕捉resolve。
原API兼容性
实现finally
Promise的finally的方法无论promise的结果是成功还是失败,都会执行,并且返回该promise。所以实现很简单。执行其promise的then方法来获取该promise的结果。
finally(callback) {
callback();
return this.then(() => {}, () => {});
}
原API兼容性
实现Promise.resolve方法(下面的方法是直接作为class的静态函数成员的)
static resolve(val) {
//如果是一个Promise
if(val instanceof Promise3){
return val
//如果没有参数,或者为null,undefined
}else if(!val){
return new Promise3((resolve, reject) => {resolve()})
//参数存在但不存在then方法
}else if(val && !val.then instanceof Function){
return new Promise3((resolve) => {
resolve(val);
});
//参数存在且存在then方法
}else if(val && val.then instanceof Function){
return new Promise3(val.then)
}
}
原API兼容性
实现Promise.reject方法
static reject(val){
//直接将val作为理由返回
return new Promise((resolve,reject)=>{
reject(val);
})
}
原API兼容性
实现Promise.all([p1, p2, p3])
static all(arr) {
let res = []
return new Promise((resolve, reject) => {
for(let i = 0, len = arr.length; i < len; i++){
arr[i].then((v) => {
res.push(v)
//是否所有的promise都是resolve
if(res.length === arr.length){
return resolve(res)
}
}, (e) => {
//只要一个reject,直接reject
return reject(e)
})
}
})
}
原API兼容性
实现Promise.race([p1, p2, p3])
static race(arr) {
return new Promise((resolve, reject) => {
for (let i = 0, len = arr.length; i < len; i++) {
arr[i].then(
(v) => {
return resolve(v)
},
(e) => {
return reject(e)
}
);
}
});
}
原API兼容性
实现Promise.allSettled([p1, p2, p3])
static allSettled(arr) {
let res = [];
let count = 0;
return new Promise((resolve, reject) => {
for (let i = 0, len = arr.length; i < len; i++) {
arr[i].then(
(v) => {
res.push({
status: 'fulfilled',
value: v,
});
},
(e) => {
res.push({
status: 'rejected',
reason: e,
});
}
).finally(() => {
//计数器必须在这里统计,因为异步操作,若放在then中,在finally中无法读取预期的值
++count === arr.length && resolve(res)
});
}
});
}
原API兼容性
实现Promise.any(p1, p2, p3)
static any(arr) {
let res = [];
return new Promise((resolve, reject) => {
for (let i = 0, len = arr.length; i < len; i++) {
arr[i].then(
(v) => {
//只要有一个resolve,则直接resolve
return resolve(v);
},
(e) => {
res.push(e);
res.length === arr.length && reject(res);
}
);
}
});
}
}
原API兼容性
最后
本人能力有限,难免出现纰漏失误,请大家指出纠正。
看看这
更多有趣文章都在公众号-全站学习小师兄。