手写 Promise 实现思路
观察 Promise 的使用方式
js
let pro = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
// or call reject(0)
}, 1000);
});
pro
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});可以分析 Promise 的内部实现大致如下:
Promise类在实例化的时候接收一个函数作为参数,计作:cb。cb能够接收两个参数:resolve方法和reject方法;cb内部逻辑执行结果成功,调用了resolve方法,则传递给.then的方法会被执行;cb内部逻辑执行结果失败,则调用reject方法,则传递给.catch的方法会被执行;Promise共有三种状态(pending、fulfilled、rejected),并且初始值为pending,并在最后从pending变为fulfilled或者从pending变为rejected;Promise支持链式调用。
实现 Promise 就要做到
- 接收一个立即执行的
cb、内部控制Promise生命周期的状态status,初始值为pending、存储使用者传递给then的参数thenCb、存储使用者传递给catch的参数catchCb; Promise实例化的时候立即执行传递个它的cb函数,cb内部根据执行结果判断是执行resolve()还是reject()函数;- 如果执行了
resolve,则将status变为fulfilled,并执行传递给then的回调函数; - 如果执行了
reject,则将status变为rejected,并执行传递给catch的回调函数; - 因为要支持链式调用,所以各个内部方法都需要返回
this;
那么 MyPromise 内部结构应该大致如下:
js
const PENDING = "pending"; //等待状态
const FULFILLED = "fulfilled"; //成功
const REJECTED = "rejected"; //失败
class MyPromise {
constructor(cb) {
this.status = PENDING;
this.thenCb = () => {};
this.catchCb = () => {};
if (cb && typeof cb == "function") {
// 函数体立即执行,但是resolve和reject是加入到微任务队列后续执行的
cb(this.resolve.bind(this), this.reject.bind(this));
} else {
throw new Error("param must be a function.");
}
}
// 改变status为fulfilled,代表cb执行成功,并执行thenCb
resolve(result) {
return this;
}
// 改变status为rejected,代表cb执行失败,并执行catchCb
reject(error) {
return this;
}
// 调用.then记录then内部回调函数,在resolve里触发;
then(thenCb) {
return this;
}
// 调用.catch记录catch内部回调函数,在reject里触发;
catch(catchCb) {
return this;
}
}然后根据分析,完善内部具体逻辑,代码如下:
js
const PENDING = "pending"; //等待状态
const FULFILLED = "fulfilled"; //成功
const REJECTED = "rejected"; //失败
class MyPromise {
constructor(cb) {
this.status = PENDING;
this.thenCb = () => {};
this.catchCb = () => {};
if (cb && typeof cb == "function") {
cb(this.resolve.bind(this), this.reject.bind(this));
} else {
throw new Error("param must be a function.");
}
}
// 将status从pending变为fulfilled,代表cb执行成功,并执行thenCb
resolve(result) {
if (this.status == PENDING) {
this.status = FULFILLED;
this.thenCb(result);
return this;
}
}
// 将status从pending变为rejected,代表cb执行失败,并执行catchCb
reject(error) {
if (this.status == PENDING) {
this.status = REJECTED;
this.catchCb(error);
return this;
}
}
// 调用.then,如果status=pending,则记录then内部回调函数,在resolve里触发;
then(thenCb) {
if (this.status == PENDING) {
this.thenCb = thenCb;
}
return this;
}
// 调用.catch,如果status=pending,则记录catch内部回调函数,在reject里触发;
catch(catchCb) {
if (this.status == PENDING) {
this.catchCb = catchCb;
}
return this;
}
}测试 MyPromise,并对比原生 Promise
js
let isOk = Math.random() > 0.5;
function asyncCb(resolve, reject, type) {
setTimeout(() => {
if (isOk) {
resolve(`${type}: Succeed.`);
} else {
reject(`${type}: Failed.`);
}
}, 1000);
}
// 测试自定义promise
let myPro = new MyPromise((resolve, reject) =>
asyncCb(resolve, reject, "手写")
);
myPro
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
// 测试原生promise
let pro = new Promise((resolve, reject) => asyncCb(resolve, reject, "原生"));
pro
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});🧯 异常出现了!
当在 cb 里同步调用 resolve 或者 reject,那么.then 或.catch 将无返回结果,如下代码:
function syncCb(resolve, reject, type) {
resolve('同步返回succeed结果')
}
// 测试自定义promise
let myPro = new MyPromise((resolve, reject) =>
syncCb(resolve, reject, "手写")
);
myPro
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});分析原因不难发现,是因为 resolve 的同步执行,导致 status 立即被改为 fulfilled,调用 this.thenCb 无效,因为此时 thenCb 还并未被.then 赋值。 要兼容这种情况,需要处理几个地方:
- 在 resolve 时,存储 result 值;在 reject 时,存储 error 值;
- 在调用.then 时,判断如果 status=fulfilled,说明 cb 里同步执行了 resolve,那么这里就需要手动调用 thenCb(result);
- 在调用.catch 时,判断如果 status=rejected,说明 cb 里同步执行了 reject,那么这里就需要手动调用 catchCb(error);
代码做如下改造:
js
const PENDING = "pending"; //等待状态
const FULFILLED = "fulfilled"; //成功
const REJECTED = "rejected"; //失败
class MyPromise {
constructor(cb) {
this.status = PENDING;
this.result = null;
this.error = null;
this.thenCb = (e) => e;
this.catchCb = (e) => e;
if (cb && typeof cb == "function") {
cb(this.resolve.bind(this), this.reject.bind(this));
} else {
throw new Error("param must be a function.");
}
}
// 将status从pending变为fulfilled,代表cb执行成功,并执行thenCb
resolve(result) {
if (this.status == PENDING) {
this.status = FULFILLED;
this.result = result;
this.thenCb(result);
return this;
}
}
// 将status从pending变为rejected,代表cb执行失败,并执行catchCb
reject(error) {
if (this.status == PENDING) {
this.status = REJECTED;
this.error = error;
this.catchCb(error);
return this;
}
}
// 调用.then,如果status=pending,则记录then内部回调函数,在resolve里触发;
then(thenCb) {
if (this.status == PENDING) {
this.thenCb = thenCb;
}
// 特殊情况:cb里直接执行了resolve,这里要手动执行thenCb
if (this.status == FULFILLED) {
thenCb(this.result);
}
return this;
}
// 调用.catch,如果status=pending,则记录catch内部回调函数,在reject里触发;
catch(catchCb) {
if (this.status == PENDING) {
this.catchCb = catchCb;
}
// 特殊情况:cb里直接执行了reject,这里要手动执行catchCb
if (this.status == REJECTED) {
catchCb(this.error);
}
return this;
}
}这么一来,在传递给 Promise 的函数里同步执行 resolve 或者 reject 也是没问题的了,可以测试下效果:
js
function syncCb(resolve, reject) {
resolve("同步返回succeed结果");
}
// 测试自定义promise
let mySyncPro = new MyPromise((resolve, reject) => syncCb(resolve, reject));
mySyncPro
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
// 立即返回结果:同步返回succeed结果用原生 Promise.all 来测试我们的 MyPromise 实例:
js
let myPro1 = new MyPromise((resolve) => resolve("myPro1"));
let myPro2 = new MyPromise((resolve) => resolve("myPro2"));
Promise.all([myPro1, myPro2])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
// 立即返回结果:[ 'myPro1', 'myPro2' ]实现 promise.all
关键点:1.保证按照执行顺序返回结果的顺序;2.结束条件判断;
js
function delay(ms = 1000) {
return new Promise((resolve) => resolve(ms), ms);
}
MyPromise.all = function (promises) {
let res = [];
let len = promises.length;
let resoleCounts = 0;
return new Promise((resolve, reject) => {
promises.map((item, index) => {
item
.then((data) => {
// res.push(data);// !!!这里不能push,没法保证顺序;
res[index] = data;
resoleCounts++;
// if (res.length == len) // !!!这里不能用结果的length,因为加入res[3] = 4,的到res=[,,,4],length也是4
if (resoleCounts == len) {
resolve(res);
}
})
.catch((err) => {
reject(err);
});
});
});
};
let p1 = delay(2000);
let p2 = delay(1000);
MyPromise.all([p1, p2]).then((ress) => {
console.log(ress);
});One More Thing
由此可见,.then 和 .catch 都是同步执行的,目的是为了在状态改变之前把它们的参数值存储到 Promise 内部的 thenCb 和 catchCb,这样在 cb 里调用 resolve 或 reject 时就能够找到对应要执行的回调函数了。
即便用户在cb里同步调用了resolve()和reject()方法,导致用户传递给.then和.catch的回调函数来不及被收集,也可以在 Promise 的.then和.catch里判断this.status == FULFILLED || REJECTED来手动执行thenCb和catchCb。

