2022-12-02
mypromise
引子
面试时如果被问到“如果没有Promise,能不能自己实现一个”,就是在深度考察对异步的理解了;
如果你的回答是“能,我封过”,则必然令人刮目相看~
事实上,作为ES6新增的API,即使没有,用户也完全可以基于想要的效果自己去实现一个;
这里给到大家我个人的实现:MyPromise;
源码地址
实现思路
将then/catch/finally中入参的回调函数按顺序收集到一个队列中;
亲自实现一下resolve和reject方法;
一旦实例resolve就将队列头部的catch回调都弹出,一旦实例reject就将队列头部的then回调都弹出,然后拿取队列头部的回调函数实行起来;
每次回调的执行结果即返回值需要继续resolve或reject一下,然后继续递归执行上述过程,直到回调队列为空;
接收任务执行器
当我们new出一个MyPromise,并内置一个任务执行器函数的时候,需要在MyPromise的构造器中接收任务执行器函数,并同步地、立即地将其执行起来;
要特别注意:任务执行器函数是同步地跑在主线程的,而不属于异步回调;
test.js
new MyPromise(
/* 任务执行器:2秒后随机履约或毁约 */
(resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve("data0") : reject(new Error("一张好人卡"));
}, 2000);
}
)
MyPromise.js
class MyPromise {
constructor(executor) {
/* 接收任务执行器并立即调用(同步调用) */
this.executor = executor;
this.executor();
}
}
定义resolve与reject
任务执行器函数会在稍后履约(resolve)或毁约(reject)
因此要告诉MyPromise履约或毁约时具体做什么
这里我们先做一下简单的打印
MyPromise.js
class MyPromise {
constructor(executor) {
/* 接收任务执行器并立即调用(同步调用) */
/* 绑定执行器函数中的resolve与reject */
this.executor = executor.bind(
this, //当前promise实例
//当前promise实例执行resolve时,this不变
MyPromise.doResolve.bind(this),
//当前promise实例执行reject时,this不变
MyPromise.doReject.bind(this)
);
this.executor();
}
/* promise实例执行resolve */
static doResolve(data) {
console.log("doResolve", data);
}
/* promise实例执行reject */
static doReject(err) {
console.log("doReject", err);
}
}
module.exports = MyPromise;
明确回调期望
明确一下异步任务履约/毁约后我们希望做什么
我们希望一旦异步任务resolve就执行then中的回调
我们希望一旦异步任务reject就执行catch中的回调
我们希望异步任务无论成功还是失败,最终都执行finally中的回调
期望如下:test.js
const MyPromise = require("./MyPromiseX")
new MyPromise(
/* 任务执行器:2秒后随机履约或毁约 */
(resolve, reject) => {
setTimeout(() => {
Math.random() > 0.1 ? resolve("data0") : reject(new Error("一张好人卡"));
}, 2000);
}
)
.then(
data => {
console.log("then1:data=",data)
return "data from then1"
// throw new Error("err from then1")
}
)
.catch(
err => {
console.log("catch1:err=",err)
return "data from catch1"
}
)
.finally(
()=>console.log("finally:game over!")
)
收集回调队列
每一个then中有一个成功回调
每一个catch中有一个失败回调
finally中有一个最终回调
我们先按顺序将这些回调收集在一个队列中
代码如下:MyPromise.js
class MyPromise {
constructor(executor) {
// 回调队列
this.callbacks = [];
/* 接收任务执行器并立即调用(同步调用) */
// 绑定执行器函数中的resolve与reject
this.executor = executor.bind(
this, //当前promise实例
//当前promise实例执行resolve时,this不变
MyPromise.doResolve.bind(this),
//当前promise实例执行reject时,this不变
MyPromise.doReject.bind(this)
);
// 立即调用任务执行器
this.executor();
}
/* promise实例执行resolve */
static doResolve(data) {
console.log("doResolve", data);
}
/* promise实例执行reject */
static doReject(err) {
console.log("doReject", err);
}
/* 收集成功回调到队列 */
then(onData) {
// 将来一旦Promise毁约 回调队列头部的所有then回调都要弹出作废
this.callbacks.push({ type: "then", callback: onData });
return this;
}
/* 收集失败回调到队列 */
catch(onErr) {
// 将来一旦Promise履约 回调队列头部的所有catch回调都要弹出作废
this.callbacks.push({ type: "catch", callback: onErr });
return this;
}
/* 收集终点回调到队列(此处假设只有一个终点回调) */
finally(onFinish) {
this.callbacks.push({ type: "finally", callback: onFinish });
return this;
}
}
module.exports = MyPromise;
此时在测试脚本test.js的末尾打印一下回调队列的话,你会看到如下输出:
[
{ type: 'then', callback: [Function (anonymous)] },
{ type: 'catch', callback: [Function (anonymous)] },
{ type: 'finally', callback: [Function (anonymous)] }
]
如果来上一堆乱序的then/catch/finally的话,则你会看到如下输出:
[
{ type: 'then', callback: [Function (anonymous)] },
{ type: 'then', callback: [Function (anonymous)] },
{ type: 'then', callback: [Function (anonymous)] },
{ type: 'then', callback: [Function (anonymous)] },
{ type: 'catch', callback: [Function (anonymous)] },
{ type: 'catch', callback: [Function (anonymous)] },
{ type: 'then', callback: [Function (anonymous)] },
{ type: 'finally', callback: [Function (anonymous)] }
]
连环作案(履约或毁约)
让我们在每次成功或失败后,都随机地继续履约或毁约起来!
test.js
const MyPromise = require("./MyPromiseX")
const p = new MyPromise(
/* 任务执行器:2秒后随机履约或毁约 */
(resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve("data0") : reject(new Error("一张好人卡"));
}, 2000);
}
)
.then(
data => {
console.log("then1:data=",data)
/* 继续履约或毁约! */
return "data from then1"
// throw new Error("err from then1")
}
)
.then(
data => {
console.log("then2:data=",data)
/* 继续履约或毁约! */
return MyPromise.resolve("data from then2")
// return MyPromise.reject("err from then2")
}
)
.then(
data => {
console.log("then3:data=",data)
/* 继续履约或毁约! */
return new MyPromise(
(resolve,reject) => setTimeout(() => {
resolve("data from then3")
// reject("err from then3")
}, 2000)
)
}
)
.then(
/* 这里本质继续履约了一个undefined */
data => console.log("then4:data=",data)
)
.catch(
err => {
console.log("catch1:err=",err)
/* 继续履约 */
return "data from catch1"
}
)
.catch(
/* 这里本质继续履约了一个undefined */
err => console.log("catch2:err=",err)
)
.then(
/* 这里本质继续履约了一个undefined */
data => console.log("then5:data=",data)
)
.finally(
/* 这里本质继续履约了一个undefined */
()=>console.log("finally:game over!")
)
// 打印一下MyPromise对象的回调队列
console.log("p.callbacks",p.callbacks);
所谓MyPromise.resolve(data)或MyPromise.reject(err)无非也就是newPromise(executor)的一个语法糖而已,实现如下:
MyPromise.js片段
/* 语法糖:创建一个立即resolve的Promise对象 */
static resolve(data) {
return new MyPromise((resolve) => resolve(data));
}
/* 语法糖:创建一个立即reject的Promise对象 */
static reject(err) {
return new MyPromise((resolve, reject) => reject(err));
}
准备执行连环回调
我们无非想要如下效果:
只要前面的环节履约了:在回调队列中找出下一个type为then的回调执行起来;
只要前面的环节毁约了:在回调队列中找出下一个type为catch的回调执行起来;
找下一个then回调前,所有位于它前面的catch都驱逐先;
找下一个catch回调前,所有位于它前面的then都驱逐先;
我们来撸码实现之!
定义MyPromise状态
class MyPromise {
/* Promise状态定义 */
static STATUS_PENDING = 0; // 挂起态
static STATUS_FULFILLED = 1; // 履约态
static STATUS_REJECTED = 2; // 毁约态
...
}
定义回调入参
constructor(executor) {
/* 回调队列 + 回调入参 (JS单线程模型=每次只能有一个回调被执行) */
this.callbacks = [];
// 定义给回调传的入参,无论是数据还是错误,我们统称为cbArg
this.cbArg = null;
...
}
定义驱逐器
成功时:从回调队列头部把所有的catch驱逐
失败时:从回调队列头部把所有的then驱逐
/*
成功时:从回调队列头部把所有的catch驱逐
失败时:从回调队列头部把所有的then驱逐
*/
shiftCallbacksWithType(type) {
/* 只要回调队列中还有回调,就把队列头部type为指定类型的回调弹出去 */
while (this.callbacks.length && this.callbacks[0].type === type) {
// 驱逐一个回调函数
this.callbacks.shift();
}
}
执行连环回调
当前任履约时
状态设置为履约态
将回调入参设置为需要履约的数据
找下一个then里的回调执行起来
/* promise实例执行resolve */
static doResolve(data) {
/* 设置状态为履约态 + 设置回调时的入参 + 拉起下一次回调 */
this.status = MyPromise.STATUS_FULFILLED;
// 回调入参为数据
this.cbArg = data;
// 拉起下一次then回调
setTimeout(() => {
this.next();
});
}
当前任毁约时
状态设置为毁约态
将回调入参设置为毁约的原因
找下一个catch里的回调执行起来
/* promise实例执行reject */
static doReject(err) {
/* 设置状态为毁约态 + 设置回调时的错误 + 拉起下一次回调 */
this.status = MyPromise.STATUS_REJECTED;
// 回调入参为错误
this.cbArg = err;
// 拉起下一次catch回调
setTimeout(() => {
this.next();
});
}
执行下一次回调
整个MyPromise最核心的逻辑来了
只要当前MyPromise为履约态,就驱逐队列头部所有的catch回调先
只要当前MyPromise为毁约态,就驱逐队列头部所有的then回调先
拿取队列头部的那个回调执行起来
next() {
/* 确定该回调哪一个callback */
if (this.status === MyPromise.STATUS_FULFILLED) {
// 履约时:弹光队列头部的catch回调
this.shiftCallbacksWithType("catch");
}
if (this.status === MyPromise.STATUS_REJECTED) {
// 毁约时:弹光队列头部的then回调
this.shiftCallbacksWithType("then");
}
/* 如果回调队列已空,则直接结束程序 */
if (!this.callbacks.length) {
console.log("回调队列已空");
return;
}
/* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */
let { callback } = this.callbacks.shift();
// 执行回调并拿到其结果(value或promise对象)
let value = callback(this.cbArg);
}
当回调返回value
test.js片段
.then(
data => {
console.log("then1:data=",data)
/* 继续履约或毁约! */
return "data from then1"
// throw new Error("err from then1")
}
)
当回调返回一个新的(非MyPromise对象)的普通value时,我们继续向后resolve它
程序会继续doResolve,doResolve内部会继续拉起下一次next
下一次next找出的回调还有新的返回值
递归了解一下!归了解一下!了解一下!解一下!一下!下!
next() {
...
// 执行回调并拿到其结果(value或promise对象)
let value = callback(this.cbArg);
/* 如果回调函数返回一个value 继续向下resolve(value) */
if (!(value instanceof MyPromise)) {
MyPromise.doResolve.call(this, value);
}
...
}
当回调抛出错误时
test.js片段
.then(
data => {
console.log("then1:data=",data)
/* 继续履约或毁约! */
// return "data from then1"
throw new Error("err from then1")
}
)
try-catch起来,把错误信息reject一下
程序会继续doReject,doReject内部会继续拉起下一次next
下一次next找出的回调还有新的返回值
递归了解一下!归了解一下!了解一下!解一下!一下!下!
next() {
...
/* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */
let { callback } = this.callbacks.shift();
/* 防止回调函数中throw错误 */
try {
// 执行回调并拿到其结果(value或promise对象)
let value = callback(this.cbArg);
/* 如果回调函数返回一个value 继续向下resolve(value) */
if (!(value instanceof MyPromise)) {
MyPromise.doResolve.call(this, value);
}else {
...
}
} catch (err) {
// 回调函数抛出错误时相当于reject(err)
MyPromise.doReject.call(this, err);
}
}
当回调返回一个新的MP对象时
.then(
data => {
console.log("then2:data=",data)
/* 继续履约或毁约! */
return MyPromise.resolve("data from then2")
// return MyPromise.reject("err from then2")
}
)
.then(
data => {
console.log("then3:data=",data)
/* 继续履约或毁约! */
return new MyPromise(
(resolve,reject) => setTimeout(() => {
resolve("data from then3")
// reject("err from then3")
}, 2000)
)
}
)
首先,MP的构造器一定会被调用!
构造器里有this.executor(),this.executor()里有resolve(data)或reject(err),也就是doResolve(data)或doReject(err)
不断递归next()找下一次回调的过程会自发地跑起
这个该死的MP对象可能自带一个回调队列
我们把当前MP对象剩余的回调队列过户给它即可
next() {
...
/* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */
let { callback } = this.callbacks.shift();
/* 防止回调函数中throw错误 */
try {
// 执行回调并拿到其结果(value或promise对象)
let value = callback(this.cbArg);
/* 如果回调函数返回一个value 继续向下resolve(value) */
if (!(value instanceof MyPromise)) {
...
}
else {
// 如果回调函数返回一个Promise对象
// 将后续所有callback过户给新的Promise对象
value.callbacks = value.callbacks.concat(this.callbacks);
}
} catch (err) {...}
}
完整的next函数
递归inside!
/*
从回调队列里拉取下一个适当的callback并回调之
这是MyPromise的核心代码:递归inside!
*/
next() {
/* 确定该回调哪一个callback */
if (this.status === MyPromise.STATUS_FULFILLED) {
// 履约时:弹光队列头部的catch回调
this.shiftCallbacksWithType("catch");
}
if (this.status === MyPromise.STATUS_REJECTED) {
// 毁约时:弹光队列头部的then回调
this.shiftCallbacksWithType("then");
}
/* 如果回调队列已空,则直接结束程序 */
if (!this.callbacks.length) {
console.log("回调队列已空");
return;
}
/* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */
let { callback } = this.callbacks.shift();
/* 防止回调函数中throw错误 */
try {
// 执行回调并拿到其结果(value或promise对象)
let value = callback(this.cbArg);
/* 如果回调函数返回一个value 继续向下resolve(value) */
if (!(value instanceof MyPromise)) {
MyPromise.doResolve.call(this, value);
}
else {
// 如果回调函数返回一个Promise对象
// 将后续所有callback过户给新的Promise对象
value.callbacks = value.callbacks.concat(this.callbacks);
}
} catch (err) {
// 回调函数抛出错误时相当于reject(err)
MyPromise.doReject.call(this, err);
}
}
完整代码
MyPromise.js
class MyPromise {
/* Promise状态定义 */
static STATUS_PENDING = 0; // 挂起态
static STATUS_FULFILLED = 1; // 履约态
static STATUS_REJECTED = 2; // 毁约态
constructor(executor) {
/* 回调队列 + 回调入参 (JS单线程模型=每次只能有一个回调被执行) */
this.callbacks = [];
// 定义给回调传的入参,无论是数据还是错误,我们统称为cbArg
this.cbArg = null;
/* 绑定执行器函数中的resolve与reject */
this.executor = executor.bind(
this, //当前promise实例
//当前promise实例执行resolve时,this不变
MyPromise.doResolve.bind(this),
//当前promise实例执行reject时,this不变
MyPromise.doReject.bind(this)
);
// 执行任务前先将Promise状态设置为pending
this.status = MyPromise.STATUS_PENDING;
// 执行任务(任务中会resolve/doResolve或reject/doReject)
this.executor();
}
/* promise实例执行resolve */
static doResolve(data) {
/* 设置状态为履约态 + 设置回调时的入参 + 拉起下一次回调 */
this.status = MyPromise.STATUS_FULFILLED;
// 回调入参为数据
this.cbArg = data;
// 拉起下一次then回调
setTimeout(() => {
this.next();
});
}
/* promise实例执行reject */
static doReject(err) {
/* 设置状态为毁约态 + 设置回调时的错误 + 拉起下一次回调 */
this.status = MyPromise.STATUS_REJECTED;
// 回调入参为错误
this.cbArg = err;
// 拉起下一次catch回调
setTimeout(() => {
this.next();
});
}
/*
成功时:从回调队列头部把所有的catch驱逐
失败时:从回调队列头部把所有的then驱逐
*/
shiftCallbacksWithType(type) {
/* 只要回调队列中还有回调,就把队列头部type为指定类型的回调弹出去 */
while (this.callbacks.length && this.callbacks[0].type === type) {
// 驱逐一个回调函数
this.callbacks.shift();
}
}
/*
从回调队列里拉取下一个适当的callback并回调之
这是MyPromise的核心代码:递归inside!
*/
next() {
/* 确定该回调哪一个callback */
if (this.status === MyPromise.STATUS_FULFILLED) {
// 履约时:弹光队列头部的catch回调
this.shiftCallbacksWithType("catch");
}
if (this.status === MyPromise.STATUS_REJECTED) {
// 毁约时:弹光队列头部的then回调
this.shiftCallbacksWithType("then");
}
/* 如果回调队列已空,则直接结束程序 */
if (!this.callbacks.length) {
console.log("回调队列已空");
return;
}
/* 拉取回调队列头部的回调函数(注意这里无视了type只解构出函数本身) */
let { callback } = this.callbacks.shift();
/* 防止回调函数中throw错误 */
try {
// 执行回调并拿到其结果(value或promise对象)
let value = callback(this.cbArg);
/* 如果回调函数返回一个value 继续向下resolve(value) */
if (!(value instanceof MyPromise)) {
MyPromise.doResolve.call(this, value);
}
else {
// 如果回调函数返回一个Promise对象
// 将后续所有callback过户给新的Promise对象
value.callbacks = value.callbacks.concat(this.callbacks);
}
} catch (err) {
// 回调函数抛出错误时相当于reject(err)
MyPromise.doReject.call(this, err);
}
}
/* 语法糖:创建一个立即resolve的Promise对象 */
static resolve(data) {
return new MyPromise((resolve) => resolve(data));
}
/* 语法糖:创建一个立即reject的Promise对象 */
static reject(err) {
return new MyPromise((resolve, reject) => reject(err));
}
/* 收集成功回调到队列 */
then(onData) {
// 将来一旦Promise毁约 回调队列头部的所有then回调都要弹出作废
this.callbacks.push({ type: "then", callback: onData });
return this;
}
/* 收集失败回调到队列 */
catch(onErr) {
// 将来一旦Promise履约 回调队列头部的所有catch回调都要弹出作废
this.callbacks.push({ type: "catch", callback: onErr });
return this;
}
/* 收集终点回调到队列(此处假设只有一个终点回调) */
finally(onFinish) {
this.callbacks.push({ type: "finally", callback: onFinish });
return this;
}
}
module.exports = MyPromise;
测试代码test.js
const MyPromise = require("./MyPromise3")
const p = new MyPromise(
/* 任务执行器:2秒后随机履约或毁约 */
(resolve, reject) => {
setTimeout(() => {
Math.random() > 0.1 ? resolve("data0") : reject(new Error("一张好人卡"));
}, 2000);
}
)
.then(
data => {
console.log("then1:data=",data)
/* 继续履约或毁约! */
return "data from then1"
// throw new Error("err from then1")
}
)
.then(
data => {
console.log("then2:data=",data)
/* 继续履约或毁约! */
return MyPromise.resolve("data from then2")
// return MyPromise.reject("err from then2")
}
)
.then(
data => {
console.log("then3:data=",data)
/* 继续履约或毁约! */
return new MyPromise(
(resolve,reject) => setTimeout(() => {
resolve("data from then3")
// reject("err from then3")
}, 2000)
)
}
)
.then(
/* 这里本质继续履约了一个undefined */
data => console.log("then4:data=",data)
)
.catch(
err => {
console.log("catch1:err=",err)
/* 继续履约 */
return "data from catch1"
}
)
.catch(
/* 这里本质继续履约了一个undefined */
err => console.log("catch2:err=",err)
)
.then(
/* 这里本质继续履约了一个undefined */
data => console.log("then5:data=",data)
)
.finally(
/* 这里本质继续履约了一个undefined */
()=>console.log("finally:game over!")
)
// 打印一下MyPromise对象的回调队列
console.log("p.callbacks",p.callbacks);
测试效果
https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c47910678fd41acba1eed2ea02bbabb~tplv-k3u1fbpfcp-watermark.image?
开班时间:2021-04-12(深圳)
开班盛况开班时间:2021-05-17(北京)
开班盛况开班时间:2021-03-22(杭州)
开班盛况开班时间:2021-04-26(北京)
开班盛况开班时间:2021-05-10(北京)
开班盛况开班时间:2021-02-22(北京)
开班盛况开班时间:2021-07-12(北京)
预约报名开班时间:2020-09-21(上海)
开班盛况开班时间:2021-07-12(北京)
预约报名开班时间:2019-07-22(北京)
开班盛况Copyright 2011-2023 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号