前言
徒手实现一个Promise是前端面试高频题,最近小编拜读了一些博客文章,get到一份最简Promise的代码,只有20行代码实现了核心异步链式调用功能(不考虑失败回调),经一番梳理把理解过程记录下来与大家分享!
实现代码
如下是最终代码,可以先一睹为快,接下来慢慢品;
function Promise(fn) {//fn是实例化时传入的,客户定义的,参数为resolve,reject的函数
this.successList = [];// 存储成功回调函数successFn,每执行一次then方法就push一个
const resolve = (value) => {
setTimeout(() => {
this.data = value;
this.successList.forEach((successFn) => successFn(value));
});
}
fn(resolve);// 需要直接执行fn, 并给其传入所需的参数,而这个参数是promise内部定义的resolve函数
}
Promise.prototype.then = function (successFn) {
// promise2
return new Promise((resolve) => {
this.successList.push(() => {
const result = successFn(this.data);// result对应的是userPromise
if (result instanceof Promise) {
// 给userPromise来个then方法执行定义成功回调
result.then((res) => {
resolve(res)
});// 或者写成result.then(resolve)
} else {
// 如果return的不是个promise,是其他值或者没有值,
// 就把这个结果值传给resolve,那么promise2中的成功回调(successFn)就可拿到并做进一步的处理
resolve(result)
}
});
})
};
剖析过程
接下来开始一步步解析,来读懂并掌握上面的源码,首先我们从Promise的使用入手,测试案例如下:
测试案例
先不考虑then的链式调用,如下是最简单的使用案例,给Promise传入一个执行函数fn,当执行到resolve时,可以在给then传入的参数函数中拿到结果值res;
const p = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 500);
})
p.then((res) => {
console.log("成功", res);
})
由此我们开始进一步思考,如果Promise是我们自己定义那应该长什么样?
- 1.是一个构造函数,能接收一个外部定义的函数并执行,这里把这个外部函数称作fn
- 2.提供一个then方法,接收一个函数,这里称作successFn,then方法可以把successFn收集起来
- 3.当执行fn时,会走到resolve(1),那么resolve这个函数也需要提供
- 4.执行resolve(1)时,之前收集起来的各个successFn开始依次执行
- 5.successFn的参数是成功的结果值res,也就是案例中的1,所以1这个值应该会被存起来,然后在successFn执行时传给它
第一步:基本实现
依据上面的思路,我们实现了如下的代码:
function Promise(fn) {//fn是实例化时传入的,客户定义的,参数为resolve,reject的函数
this.successList = [];// 存储成功回调函数successFn,每执行一次then方法就push一个
const resolve = (value) => {
this.data = value;
this.successList.forEach((successFn) => successFn(value));
}
fn(resolve);// 需要直接执行fn, 并给其传入所需的参数,而这个参数是promise内部定义的resolve函数
}
Promise.prototype.then = function (successFn) {
this.successList.push(() => {
successFn(this.data);// 这里this.data就是"p.then((res) => {...}"中的res,起初并不知道res的值是多少,执行了resolve才知道
});
};
第二步:细化代码
再基于上述的代码,我们继续考虑如下几点:
- 1.Promise是异步的,那么其中resolve函数里的实现应该也是异步的,我们可以用setTimeout;
- 2.现在需要考虑
p.then(()=>{}).then(()=>{})这样的链式操作,那么在then方法里一定是返回了一个新的Promise,这样才能继续调用其的then方法; - 3.同时为了测试链式操作,测试示例也要在then的成功回调函数(successFn)里增加返回一个Promise的逻辑进行测试使用; 所以,测试示例如下:
// promise1
const p = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 500);
})
p.then(function (res) {
console.log("成功1", res);
// userPromise
return new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 500);
});
}).then(function (res) {
console.log("成功2", res);
})
// 期望得到的结果是
// “成功1 1”
// “成功2 2”
根据上面说的第一点,需要把Promise构造函数改成如下代码:
function Promise(fn) {//fn是实例化时传入的,客户定义的,参数为resolve,reject的函数
this.successList = [];// 存储成功回调函数successFn,每执行一次then方法就push一个
const resolve = (value) => {
setTimeout(() => {
this.data = value;
this.successList.forEach((successFn) => successFn(value));
});
}
fn(resolve);// 需要直接执行fn, 并给其传入所需的参数,而这个参数是promise内部定义的resolve函数
}
then方法实现
由于要实现链式操作,那么then方法必须返回一个promise,那么可以暂时先写成下面的样子:
Promise.prototype.then = function (successFn) {
// promise2
return new Promise((resolve) => {
console.log("push ----")
this.successList.push(() => {
successFn(this.data);
});
})
};
接下来为了方便解释, 给不同位置的promise起了名字,在代码中有标注promise1,promise2和userPromise:
p=new Promise()=>promise1- Promise.prototype.then中的
return new Promise()=>promise2 - 测试示例
p.then(function (res) {return new Promise(...)})中的return new Promise()=>userPromise
同时看着测试示例,我们来梳理一下:
- 1.执行第一个
p.then()方法时,开始创建promise2,同时在promise2中执行的this.successList.push()会把成功回调函数successFn放到promise1的successList中待执行,所以下面console.log("成功1", res)的地方还未执行。
p.then(function (res) {
console.log("成功1", res);
// userPromise
return new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 500);
});
})
- 2.第一个
p.then()执行完后,已经返回了promise2,那么可以继续执行第二个p.then(),那么此时是在promise2的successList放入了带执行成功回调; - 3.当过了500ms,
promise1开始执行resolve时,那么接着就拿出promise1中的successList,遍历依次执行成功回调函数 - 4.执行到上面
successFn(this.data)这句时,也就是开始执行如下这个函数:
function (res) {
console.log("成功1", res);
// userPromise
return new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 500);
});
}
打印完“成功1 1”后,又开始创建userPromise,并执行userPromise中传的Fn函数,也就是如下这段:
(resolve) => {
setTimeout(() => {
resolve(2);
}, 500);
}
接着就是等待500ms后执行userPromise的resolve(2),此时你会发现successFn(this.data)这句执行完时,得到的是创建并返回userPromise。
但是,重点来了!
-
5.
userPromise并没有then方法去到自己的successList中push对应的成功回调函数,那么当resolve(2)时,没有可执行的成功回调! -
6.并且,由上面第2点知道,我们把第二个
p.then(successFn)中的successFn给了promise2
所以,现在要做的就是!
-
7.给
userPromise一个then方法执行,让userPromise有成功回调函数,当userPromise成功回调时就去通知promise2让其也执行resolve() -
8.综上得到的结果是
userPromise成功resolve时,promise2也成功resolve,这样之前放在promise2上的successList就会依次遍历执行其函数项。
最后,再经过修改,then方法的终极版如下:
Promise.prototype.then = function (successFn) {
// promise2
return new Promise((resolve) => {
console.log("push ----")
this.successList.push(() => {
const result = successFn(this.data);// result对应的是userPromise
if (result instanceof Promise) {
// 给userPromise来个then方法执行定义成功回调
result.then((res) => {
resolve(res)
});// 或者写成result.then(resolve)
} else {
// 如果return的不是个promise,是其他值或者没有值,
// 就把这个结果值传给resolve,那么promise2中的成功回调(successFn)就可拿到并做进一步的处理
resolve(result)
}
});
})
};
总结
通过battle一遍代码感受到这样一个思路,我们外部使用的时候是只看到了promise1和userPromise,内部使用一个promise2将promise1和userPromise链接起来了!
分享了这个最简Promise的代码思路,希望对你有帮助!如有疏漏,请不吝赐教~ 😊