该系列的主要目的是帮助很多写代码一知半解的同学,认为自己懂了,当真正遇到深入点的问题的时候,还是要靠搜索来解决问题。这个系列的主要目的就是告别搜索,让自己写出的代码能够做到信心满满。不论问题怎么变化我们都能360度无死角解答。
该系列的计划可以在github/core-rewrite上看到。欢迎多多交流。 同时,字节电商杭州团队大量招聘,来加入吧!详细信息可扫最下面公众号
Promise核心实现
这是该代码实现系列的第一期 - Promise核心实现
更多问题欢迎多多在Promise核心实现讨论区进行讨论
前提准备
这一次我们是要实现的是Promise,帮助我们进行更加深入的了解Promise。
在这之前需要你理解什么是Promise以及Promise常用的用法,如果你不了解Promise可以点击这里进行了解
写前思考
在实现之前我们要先思考下实现Promise包括哪些点,比如
- Promise代表异步结果
- Promise有三种状态,状态只能从
pending到fulfilled或者pending到rejected - Promise可以链式调用
到这里想到这三个点,我们就可以开始动手实现了,让我们一步步来看
代码实现
构造函数实现
首先,我们来定义一个函数,为了不跟原有的Promise冲突我们命名为CorePromise
function CorePromise(handler) {
// write code here
}
首先我们定义Promise的状态,Promsie初始状态是pending。
同时我们知道Promise构造函数接受一个参数,该参数是一个函数,它会接受resolve和reject两个参数,方便我们进行调用,按这种逻辑我们实现成下面的样子
function CorePromise(handler) {
// 三种状态pending, rejected, fulfilled
this.status = 'pending';
function resolve() {
}
function reject() {
}
handler(resolve, reject);
}
当调用resolve或reject的时候会发生什么那?promise的状态会发生变化,我们在之前的基础上实现resolve和reject函数,他们只会在pending状态会执行
同时由于是Promise需要异步执行,所以我们这里使用setTimeout进行包裹。
调用后我们记录成功后的结果和失败的原因。
function CorePromise(handler) {
// 三种状态pending, rejected, fulfilled
this.status = 'pending';
this.value = null;
this.err = null;
const resolve = (value) => {
if (this.status === 'pending') {
setTimeout(() => {
this.status = 'fulfilled';
this.value = value;
})
}
}
const reject = (err) => {
if (this.status === 'pending') {
setTimeout(() => {
this.status = 'rejected';
this.err = err;
})
}
}
handler(resolve, reject);
}
调用resolve或reject除了状态变化,我们还会继续执行调用then里面定义的回调函数,并将结果或失败原因传递给该回调函数。
下面我们定义两个数组来挂载回调函数,并在执行的时候遍历执行它们。
这里另外对 handler(resolve, reject)进行了错误处理,当其直接报错的时候我们将Promise直接reject掉。
function CorePromise(handler) {
// 三种状态pending, rejected, fulfilled
this.status = 'pending';
this.value = null;
this.err = null;
this.resolveCbs = [];
this.rejectCbs = [];
const resolve = (value) => {
if (this.status === 'pending') {
setTimeout(() => {
this.status = 'fulfilled';
this.value = value;
this.resolveCbs.forEach(cb => cb(this.value));
})
}
}
const reject = (err) => {
if (this.status === 'pending') {
setTimeout(() => {
this.status = 'rejected';
this.err = err;
this.rejectCbs.forEach(cb => cb(this.err));
})
}
}
try {
handler(resolve, reject);
} catch(err) {
reject(err)
}
}
到这里我们构造函数实现就差不多了,下面来看then的实现。
then实现
then的主要目标是挂载回调函数,当Promise状态变化的时候,能够执行这些函数。
同时由于then支持链式调用,所以then本身也需要返回一个新的Promise。
then接受两个参数,第一个来处理Promise成功后的结果,第二个用来处理Promise失败后的结果,实现上当这两个函数不存在的时候我们赋予其默认值,直接将结果或错误向后传递。
CorePromise.prototype.then = function(resolveCb, rejectCb) {
let promise = null;
resolveCb = typeof resolveCb === 'function' ? resolveCb : (value) => value;
rejectCb = typeof rejectCb === 'function' ? rejectCb : (err) => { throw err };
promise = new CorePromise((resolve, reject) => {
});
return promise
}
现在到了最重要的部分,实现then逻辑。这里有两点
- 如果
Promise已经是终态了,则直接进行将then返回的Promse也转变为终态。 - 如果当前
Promise还处在pending状态,那么我们就将处理函数保存在Promise的回调数组中,以供进入终态后调用。
重要:这里涉及到两个Promise实例注意区分,分别是当前Promise和then中返回的新Promise,返回的新Promise在Promise状态变化后也进行变化
基本到这里我们核心就实现完毕了,但考虑各种情况我们还需要额外处理下特殊情况
- then的回调函数结果又是一个
Promise实例(这与之前的提到的两种Promise都不同,属于第三个,这里稍微有点绕,需要好好思考清楚) - then的回调函数结果的
Promise与当前then返回的Promise实例是同一个会造成循环调用,需要抛出错误
CorePromise.prototype.then = function (onFulfilled, onRejected) {CorePromise.prototype.then = function (onFulfilled, onRejected) {
let promise = null;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
onRejected = typeof onRejected === 'function' ? onRejected : (err) => { throw err };
const handlePromise = (modifier, resolve, reject) => value => {
try {
const x = modifier(value);
if (x === promise) {
// 相同对象会循环调用,直接报错
reject(new TypeError('Chaining cycle detected for promise!'))
return;
}
// x是thenable
if (x && typeof x.then === 'function') {
x.then(resolve, reject);
} else {
// x不是thenable
resolve(x);
}
} catch(err) {
reject(err);
}
}
promise = new CorePromise((resolve, reject) => {
if (this.status === 'fulfilled') {
handlePromise(onFulfilled, resolve, reject)(this.value);
} else if (this.status === 'rejected') {
handlePromise(onRejected, resolve, reject)(this.err);
} else {
this.resolveCbs.push(handlePromise(onFulfilled, resolve, reject));
this.rejectCbs.push(handlePromise(onRejected, resolve, reject));
}
});
return promise
}
处理完特殊情况,我们最后再实现先catch函数,它可以获取到Promise调用链中的错误,实现起来也很简单,其实就是then的一个语法糖
CorePromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
Promise常用函数实现
我们平时还会常用到的Promise.resolve、Promise.reject、Promise.all和Promise.race都来实现下。前两者都只不过是一个语法糖
Promise.resolve
Promise.resolve实现,返回一个立即进入resolve状态的Promise
CorePromise.resolve = (value) => {
return new CorePromise((resolve) => {
resolve(value)
})
}
Promise.reject
Promise.reject实现,返回一个立即进入reject状态的Promise
CorePromise.reject = (error) => {
return new CorePromise((resolve, reject) => {
reject(error)
})
}
Promise.all
Promise.all实现起来也不难,有两点需要注意
- 当一个
Promise进入reject状态,则整个结果Promise进入reject状态 - 当所有
Promise完成返回结果的时候,注意结果数组的返回顺序,这就是代码里没有直接把结果push进数组而是使用result[i] = res这种方式并配合total计数来实现的原因
CorePromise.all = (promises) => {
const result = []
const length = promises.length;
let total = 0;
return new CorePromise((resolve, reject) => {
for (let i = 0; i < length; i++) {
const promise = promises[i];
promise.then(res => {
result[i] = res;
total++;
if (total === length) {
resolve(result);
}
}, (err) => {
reject(err);
})
}
})
}
Promise.race
Promise.race实现,哪个Promise先执行则直接忽略掉其他Promise
CorePromise.race = (promises) => {
let isHandled = false
return new CorePromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then((res) => {
if (!isHandled) {
isHandled = true;
resolve(res)
}
}, reject)
}
})
}
好好读书
招聘&前端交流,也可直接加vx: stellarecho