手写 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
。