《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之
引文:关于手写源码这件事,几乎是每个抠图仔逃不了的命运,手写Promise也是一样。但是也看到好多人说手写源码完全是浪费时间,可是很多时候你发发现收藏 !== 学会!抛开面试不说,通过源码习得的思想或许才是我们真正需要汲取的养分!况且能自己实现一个工具库或者Api不也是一件令人无比兴奋的事吗?😏
而手动实现一个Promise网上的资料非常之多,可是要么是功能不够全面,要么就是没有过程,于是我决定从0-1实现一个完整版的Promise,包括函数式和Class两个版本(每个分支管理一个版本,包含当前版本非常详尽的迭代文档和源码)
主打一个你不想看懂都难!
Promise是什么?
想要动手写一个Promise,我们首先得知道 Promise 是什么?我们都知道Promise是es6的新特性,阮一峰老师的这篇Promise文章堪称吾辈之楷模!无比详尽的介绍了Promise的实现,初学者可以看看这篇文章。
从0-1开启你的Promise实现之旅
我们将通过6个版本的迭代,对应王者农药中“六个段位” 😏 逐步实现函数式和class两个版本的Promise,最终会实现一个具备如下功能的Promise,目标是无限趋近于Promise,后期会借鉴TDD的思想,接入测试用例以及其它特性(欢迎一起共建) 快来看看你在哪个段位吧?
看一眼得了,别太较真哈,不然容易被劝退咯!
那我们废话不多说,直接开始吧?
1.0版本Promise(倔强废铁)
目标
请先认真看一下我们1.0版本的目标,字数不多,1min不到,目的就是搭好架子(这一步是理解Promise的第一步,也是我认为最重要的一步)。
实现
足够简单吧?至此,我们Promise的架子已经搭起来啦!不管做任何事情,如果你觉得有难度,那一定就是拆分的不是足够小足够细,你细品是不是这个道理😏
2.0版本Promise(傲娇白银)
目标
2.0的Promise是时候上点硬菜啦?我们需要做的比较多,我们需要实现resolve和reject,也需要实现实例对象的then和catch方法!另外构造器函数内部我们需要一个变量保存resolve和reject的执行结果,也需要支持基本的异常处理。
实现
构造器相关属性
this.PromiseState = "pending";
// 保存Promise对象的结果
this.PromiseResult = null;
// 保存异步操作场景的回调函数
this.callbacks = [];
// resolve和reject普通函数内部的this指向的是window对象,所以需要保存this
let self = this;
实现resolve
* 成功的回调函数
* @param {*} data
* @returns
*/
function resolve(data) {
// Promise的状态只能被修改一次
if (self.PromiseState !== "pending") return;
// 修改Promise对象的状态为fulfilled;
self.PromiseState = "fulfilled";
self.PromiseResult = data;
// 批量执行异步操作场景成功的回调函数
self.callbacks.forEach((cb) => {
cb.onResolved(data);
});
}
实现reject
* 失败的回调函数
* @param {*} data
* @returns
*/
function reject(data) {
// Promise的状态只能被修改一次
if (self.PromiseState !== "pending") return;
// 修改Promise对象的状态为rejected;
self.PromiseState = "rejected";
self.PromiseResult = data;
// 批量执行异步操作场景失败的回调函数
self.callbacks.forEach((cb) => {
cb.onRejected(data);
});
}
可能部分同学会有疑问:
- 为什么要保存PromiseResult呢?哪里才会用到呢?
- resolve和reject中批量执行的callbacks是哪里来的呢?
- 为啥callbacks是个数组呢?
别急,等我们实现then了方法,谜底自会全部揭晓!带着这3个问题,我们一起来实现一下then方法
实现then方法
* 返回一个新的Promise对象
* @param {*} onResolved
* @param {*} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
if (this.PromiseState === "fulfilled") {
onResolved(this.PromiseResult);
}
if (this.PromiseState === "rejected") {
onRejected(this.PromiseResult);
}
// 保存异步操作场景的回调函数
this.callbacks.push({
onResolved,
onRejected,
});
});
};
实现了then方法,相信前面的3个问题已经清楚了吧?
- 保存PromiseResult是因为then方法的两个回调函数中需要Promise的执行结果!
2. 关于为啥要存callback的问题,以及为啥要用数组保存而不是对象,我们留个彩蛋?可以在评论区留下你的答案,也可以看看别人的优秀答案😏
实现catch方法
catch方法就比较简单了,因为我们已经实现了then,我们可以通过给then方法第二个参数传undefined的方式,直接服用then方法的第二个回调。
* Promise实例对象的catch方法,也返回一个新的Promise对象,只是指定失败的回调函数
* @param {*} onRejected
* @returns
*/
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
};
现在我们的Promise已经具备了基本处理resolve和reject的能力,then中也能拿到resolve或reject的结果了,但是我们知道Promise的then的执行结果也是一个Promise对象,那then的结果长啥样呢?
先来看看官方的Promise的实现:
嗯!是个Promise对象,而且是个fulfilled状态的Promise对象。再来看看我们自己的then:
嗯?虽然也是个Promise对象,但是为啥是pending状态呢?再回到上面回顾一下我们实现的then,我们只是执行了相应的回调函数,并没有改变then这个Promise的状态以及处理其返回结果呀?
这里还有一个问题:为啥PromiseResult的结果是undefined呐?显然,我们需要带着这个问题,继续完善一下我们的then方法。
3.0版本Promise(出土黄金)
目标
这个版本我们只需带上上面的问题,完善一下then方法,核心目标是实现then方法内部状态和执行结果的处理,而then方法的结果和状态取决于其内部回调函数的执行结果,而这个执行结果通常分为3种:仍然是一个Promise对象、是一个普通值以及throw的错误。
实现
* 返回一个新的Promise对象
* @param {*} onResolved
* @param {*} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
const self = this;
return new Promise((resolve, reject) => {
const callback = function (fnType) {
try {
let result = fnType(self.PromiseResult);
if (result instanceof Promise) {
result.then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
};
// 保存成功和失败的回调函数
if (this.PromiseState === "fulfilled") {
callback(onResolved);
}
if (this.PromiseState === "rejected") {
callback(onRejected);
}
// 保存异步操作场景的回调函数
this.callbacks.push({
onResolved,
onRejected,
});
});
};
现在,我们的then也可以有自己的状态和结果啦!
再来试试返回普通值以及Promise场景的结果:
全部符合预期,都可以正常处理啦,欧耶!🍻
革命尚未结束,同志仍需努力呀!下一个版本,我们需要:
- 处理then回调函数中存在的异常穿透问题以及onResolved不传的问题
- 新增Promise的一系列热门方法:resolve、reject、all、race等
- 新增实例方法的finally方法。
let's play, let's go!
4.0版本Promise(酷炫铂金侠)
目标
目前上面已经大致介绍,大纲来啦!
实现
继续完善then方法,使其更强大
* 返回一个新的Promise对象
* @param {*} onResolved
* @param {*} onRejected
*/
Promise.prototype.then = function (onResolved, onRejected) {
const self = this;
// 如果不传onResolved函数,就默认返回成功的结果
if (typeof onResolved !== "function") {
onResolved = (value) => value;
}
// 支持catch方法的异常穿透处理,如果不传onRejected函数,就默认抛出异常
if (typeof onRejected !== "function") {
onRejected = (err) => {
throw err;
};
}
return new Promise((resolve, reject) => {
const callback = function (fnType) {
try {
let result = fnType(self.PromiseResult);
if (result instanceof Promise) {
result.then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
};
// 保存成功的回调函数
if (this.PromiseState === "fulfilled") {
callback(onResolved);
}
// 保存失败的回调函数
if (this.PromiseState === "rejected") {
callback(onRejected);
}
// 保存异步操作场景的回调函数,先存起来,等到Promise对象的状态改变时再执行
if (this.PromiseState === "pending") {
this.callbacks.push({
onResolved: function () {
callback(onResolved);
},
onRejected: function () {
callback(onRejected);
},
});
}
});
};
这版我们新增:
以及
新增实例对象的finally方法
* Promise实例对象的finally方法,返回一个新的Promise对象
* 1. 无论成功还是失败都会执行的回调函数
* 2. 返回的Promise对象的状态和结果取决于传入的Promise对象的状态和结果
* 3. 如果原始 Promise 成功,执行回调函数,然后返回一个新的 Promise,状态为 fulfilled,值为原始 Promise 的值
* 4. 如果原始 Promise 失败,执行回调函数,然后返回一个新的 Promise,状态为 rejected,值为原始 Promise 的失败原因
* @param {*} callback
* @returns
*/
Promise.prototype.finally = function (callback) {
return this.then(
(value) => {
return Promise.resolve(callback()).then(() => value);
},
(reason) => {
return Promise.resolve(callback()).then(() => {
throw reason;
});
}
);
};
新增Promise的resolve方法
* Promise.resolve方法,返回一个Promise对象,其状态和结果取决于传入参数的类型:
* 1. 如果是一个Promise对象,那么返回的Promise对象的状态和结果取决于传入的Promise对象的状态和结果
* 2. 如果是一个普通值,那么返回的Promise对象是fulfilled的Promise,结果就是传入的值
* @param {*} data
* @returns
*/
Promise.resolve = function (data) {
return new Promise((resolve, reject) => {
if (data instanceof Promise) {
data.then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
} else {
resolve(data);
}
});
};
新增Promise的reject方法
reject比较特殊,不管你传入什么都不好使,结果始终是rejected的Promise对象
* Promise.reject方法,返回一个Promise对象
* 1. 不管传入的参数是什么,结果都是失败
* 2. 返回的Promise对象的状态是rejected
* 3. 返回的Promise对象的结果就是传入的参数
* @param {*} data
* @returns
*/
Promise.reject = function (data) {
return new Promise((resolve, reject) => {
reject(data);
});
};
新增Promise的all方法
all只需铭记一点:所有成功则成功,只要有一个失败则失败。成功时返回所有Promise执行结果组成的数组,失败时返回失败的那一个的执行结果。
* Promise.all方法,用于指定多个Promise对象同时执行:
* 1. 只有全部成功才会成功,此时返回的Promise对象的结果是一个由所有Promise结果组成的数组,数组的顺序和传入的Promise对象的顺序一致
* 2. 只要有一个失败就会失败,此时返回的Promise对象的结果是失败的Promise对象的结果
* 3. 返回一个新的Promise对象
* @param {*} promises
*/
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0;
let result = [];
for (let index = 0; index < promises.length; index++) {
promises[index].then(
(res) => {
count++;
// 不采用push的方式,因为push的方式会改变数组的顺序
result[index] = res;
if (count === promises.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
}
});
};
新增Promise的race方法
这让我想起了最近的“辛🐑大战”,谁先赢得市场先机谁就握有最高定价权和话语权!race的状态和结果取决于“跑的最快”的那个Promise对象!
* Promise.race方法,返回一个 Promise 对象,谁第一个改变状态谁就有决定权!也就是决定 race 最终的状态和结果:
* 1. 只要有一个成功就会成功,此时返回的Promise对象的结果是第一个成功的Promise对象的结果
* 2. 只要有一个失败就会失败,此时返回的Promise对象的结果是第一个失败的Promise对象的结果
* 3. 取决于第一个改变状态的Promise对象
* @param {*} promises
* @returns
*/
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let index = 0; index < promises.length; index++) {
promises[index].then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
}
});
};
相信被面试官拷打过的抠图仔们都知道,“Promise的构造器是同步执行的,但是then方法是异步执行的”,那我们看看我们实现的是不是也这样呢?
写一个非常简单的demo测试一下,很显然,我们期望输出:111 => 222 => 333
实际输出:111 => 333 => 222 ,打脸来的如此之快?几个意思?🙄
仔细回顾一下就会发现:我们在resolve和reject中改变完状态后,压根就没有通过异步执行then呀,而是立刻就执行了then😱。那就继续完善呗?
5.0版本Promise(精灵钻石怪)
目标
5.0的目标很简单,将所有then、catch以及resolve和reject执行后callbacks中存储的方法的执行时机统统调整为异步。
实现
其它几处都是相同的处理方式,不再重复演示。
至此,我们已经实现了Promise几乎所有常用的功能。什么?函数式的实现太low ?那再来改一个class版,说干就干!👊🏻
6.0版本Promise(假冒王者尊)
目标
改造为class版本的Promise
实现
重点在于实现的过程和思路,改造相对来说就容易太多啦!
至此,我们手写版的Promise就已告一段落。是不是觉得Promise也没那么难搞呢?😏。当然我们实现的只是mini版的,属于“山寨版的Promise”,跟Promise本尊肯定是有一定的差距的!但是首先我们是麻雀虽小但也五脏俱全咯!而且要想无限靠近Promise本尊,还需各位的相助共建。
《重学JavaScript专栏》其它文章推荐:
转载自:https://juejin.cn/post/7416723051377033252