共计 5328 个字符,预计需要花费 14 分钟才能阅读完成。
前言: 最初的章节终于要来了,不晓得有没有人真正跟进上来这个进阶系列。最后我的本意只是想间接从 《手撕 Promise》 开始。然而我想了想,这样的话对一些基础知识不是特地牢固的小伙伴不太敌对,于是就创立了该专栏尽量从头开始讲给那些真正想从我的博文中学习到那么一丢丢货色的人
浏览本文前须要领有咱们后面的六个 进阶工作 的通关钥匙🔑 (0/6)
- JS 代码运行机制
- 加深了解回调函数
- 手写“回调天堂”
- 宏工作和微工作
- 手写 Promise(前篇)
- 手写 Promise(中篇
请追随本文实现你登神长阶的最初一步
一. 剖析 MyPromise 现有的问题
- 如果你跟进了前篇的常识,那么你目前的代码应该是上面这个样子。
- 尽管看起来即实现了存储同步数据的性能,又实现了存储异步数据的性能,然而到目前为止,咱们的 MyPromise 还是一个假的 Promise。什么意思呢?
- 咱们先看一下原生的 Promise 存储一个同步数据时的样子。
依据之前的常识,在主线程的
console.log('我在主线程,我应该是第一执行')
必定是会比Promise
实例的then
办法的console.log
执行的快的,因为一个是在主线程,一个还得在微工作队列里排一会队。咱们能够很快得出,在控制台的后果应该如下: - 当初咱们测试一下咱们的刚刚写好的
MyPromise
的状况是什么样子的。如果咱们的 MyPromise 是现实状态下的成果,resolve
办法应该会被放入微工作队列。所以上面的代码依照现实状况应该是先输入console.log(我应该是第一次执行)
这行代码,而后输入数字 1。然而后果却是:
- 这该怎么办呢?🤔
二. 微工作的创立
- 如果你之前认真读过我的《宏工作和微工作》这篇文章,那么你肯定晓得创立一个微工作其实十分非常简单。没错,就是应用 window 对象身上的
queueMicrotask
函数。 - 说干就干,咱们的数据是在哪读的? 你还记得吗?没记起来我揭示你一下,应该是在
then
办法的第一个回调函数onFulfilled
函数中的吧?那不就非常简单了吗?我间接在
state==='fulfilled'
的时候,也就是数据填充好当前,我将onFulFilled
函数从原来的 同步执行代码 逻辑转变为 放入微工作队列去执行。哦哦,对了,别忘了咱们resolve
函数也执行了一个非凡的onFulfilled
函数。那么这里同理,把读取数据的工作放入 微工作队列 里去执行。
- 这时候咱们测试一下,看看是不是咱们想的那样。
- 下面是保留同步数据的状况,咱们再测试一下保留异步数据的状况。
上面是控制台输入后果:
完满!是咱们想达到的成果~
三. 屡次调用 then 办法的后果
- 看似目前咱们这个
MyPromise
如同有那味儿了。然而咱们剖析一下上面的一种状况。咱们还是依据原生Promise
来推断。 - 留神! 这里不是
then
办法的链式调用,而是一个Promise
实例屡次调用then
办法的写法。(这里不要和链式调用搞混了)让咱们看一下控制台的状况:
能够看到,咱们执行了三次
then
办法,它就帮咱们执行三次读取数据的操作。然而反过头来看一下咱们的MyPromise
是什么行为。 - 依照咱们现实的成果,控制台应该会在 2 秒后输入三个
我是 MyPromise
。ok 咱们测试一下:
能够看到,咱们的控制台在 2 秒后只输入了一次
MyPromise
。这是怎么回事呢?🤔
四. 剖析 Bug 产生的起因
- 不焦急,咱们一步一步剖析代码的执行程序。首先会执行
MyPromise
里executor
函数的代码。 - 而后代码会执行到
setTimout
,而后会把resolve
放入宏工作队列里去期待 2 秒。 - ok,接下来该执行上面三个间断的
then
办法。留神接下来是全文第一个重点: 关键点就在于咱们三个
then
是同步代码,肯定要搞清楚这回事。明确了这一点,咱们就须要去看MyPromise
类里到底产生了什么状况。 - 因为咱们的
resolve
还在工作队列里排着队,那么在两秒内这时候的state
百分之一百还是pending
状态。能够咱们的
then
是要持续走的啊,这里须要接着往下咱们看then
办法里的逻辑。因为咱们这时候的state==='pending'
所以上面的逻辑咱们压根不必思考,只须要思考第一个if
语句里的代码即可。 - 关键点就在于这里这段代码。
我执行的三次
then
在这里相当于执行了三次赋值操作。什么意思呢? - 没看懂?没关系,我接下来这种写法你肯定能够看明确什么意思。
你感觉控制台会为你别离执行三次吗?
看懂了这个例子,其实你就明确为什么咱们的后果只会输入咱们最初一次调用的
then
办法的后果了。因为们后面两次都被第三次所笼罩了。所以咱们才会看到上面个后果,只输入了一个
result3
。
五. 解决 Bug
- 那当初这个事件怎么解决呢?没错,数组!咱们只须要将原来的
callBackFn
由一个单纯的变量,革新成一个数组。 - 紧接着去革新咱们的
then
办法。稍等,我想你有很大概率会依照下面的写法这样去写,其实这样是 十分谬误 的,你这样的逻辑是把
onFulfilled(this.$result)
函数 执行的后果 推动数据中去,而不是把一个函数推动数组中去。所以正确的写法应该是咱们用 箭头函数 包装一层,如下所示。下面的代码含意是,如果我在保留异步数据,因为的
state
不能第一工夫从pending
状态扭转,那我就先把一个箭头函数推动一个叫callBackFnArray
的数组中去。 - 那么去哪里执行咱们数据里沉积的回调函数呢?这里咱们就须要去革新一下咱们的
resolve
函数。咱们须要把下面的老代码革新成上面的样子。
咱们用数组身上的
forEach
失去每个箭头函数,而后顺次调用即可。 - ok, 咱们还是测试这个例子看一下咱们的思路是否正确。
上面是控制台输入成果。
是的,咱们胜利解决了这个辣手的问题。
六. then 函数能够链式调用的起因
- 咱们晓得原生 Promise 最大的特点就是能够容许咱们间断
then
上来。如下所示。咱们不能光晓得能够这样用,还要明确一点为什么能够这样用。首先,咱们
then
函数的调用者是不是一个 Promise 实例啊? - 那第二个
then
办法能调用,是不是就意味着这串代码也是一个 Promise🤔?怎么验证呢?非常简单,咱们打印一下它的返回值不就能够了吗?说干就干,咱们用一个变量
test
去接管第一次then
办法的返回值后果看看是个什么。咱们看一下控制台。果然是一个 Promise
- 剖析完原生的 Promise,咱们再剖析一下咱们的
MyPromise
。很可怜的是,咱们的
then
返回的是一个undefined
。undefined
身上有then
办法吗?显然是没有的,如何解决?咱们接着往下看。目前为止,你的代码应该是这个样子。为了不便你们进行下一步,我间接贴上源码,如果你们有哪一步没跟上,能够依据我当初的代码做出一些调整。
class MyPromise {
#result: any;
#state: "pending" | "fulfilled" | "rejected";
#callBackFnArray: Array<any>;
constructor(executor: Function) {this.#callBackFnArray = [];
this.#state = "pending";
executor(this.resolve.bind(this), this.reject.bind(this));
}
resolve(value) {if (this.#state !== "pending") return;
this.#result = value;
this.#state = "fulfilled";
if (this.#callBackFnArray.length > 0) {queueMicrotask(() => {this.#callBackFnArray.forEach((onFulfilled) => {onFulfilled();
});
});
} else {return;}
}
reject(value) {if (this.#state !== "pending") return;
this.#result = value;
this.#state = "rejected";
}
then(onFulfilled, onRejected) {if (this.#state === "pending") {this.#callBackFnArray.push(() => {onFulfilled(this.#result);
});
} else if (this.#state === "fulfilled") {queueMicrotask(() => {onFulfilled(this.#result);
});
} else if (this.#state === "rejected") {onRejected(this.#result);
}
}
}
六. 实现 then 函数的链式调用
-
既然咱们晓得了要想实现
then
办法的链式调用,那么then
办法自身的返回值就也须要是一Promise
实例。那还想什么呢,咱们间接看then
函数的构造体。咱们间接返回一个新的
MyPromise
实例,而后 最最最重要的关键点,也是咱们之前始终在强调的一点,你肯定要记住!MyPromise 的参数
execurtor
是一个会立刻执行的一般函数。 - 既然会立刻执行,咱们把之前
then
函数里的那逻辑代码放进去是不是没有任何影响?操作非常简单,只须要把之前的代码复制粘贴进咱们的executor
函数内即可。< /br>后果如下:
- 这仅仅是第一步,看过我之前剖析 -《手写“回调天堂”》的读者都晓得。咱们下一个
then
办法读取的后果是上一个onFulfilled
或者onRejected
的返回值。那么咱们在这里如何读取呢? - 留神我下面这两段代码的含意。
这个函数的执行后果是不是就代表着
onFulfilled
函数执行结束的返回值呢?当上面这个两个箭头函数执行的时候,就会将onFulfilled(this.#result)
的后果通过回调函数的模式传递给咱们。这里的确是一个难点,须要读者粗浅去领会 下一个
resolve
为了去保留数据而又作为了一个回调函数的意思。 - 最初咱们测试一下是否能够失常链式调用。
通过含辛茹苦,最初咱们实现了
then
的链式调用
七. 源码
在这里贴出咱们最终实现的 MyPromise
源码。
class MyPromise {
#result: any;
#state: "pending" | "fulfilled" | "rejected";
#callBackFnArray: Array<any>;
constructor(executor: Function) {this.#callBackFnArray = [];
this.#state = "pending";
executor(this.resolve.bind(this), this.reject.bind(this));
}
resolve(value) {if (this.#state !== "pending") return;
this.#result = value;
this.#state = "fulfilled";
if (this.#callBackFnArray.length > 0) {queueMicrotask(() => {this.#callBackFnArray.forEach((onFulfilled) => {onFulfilled();
});
});
} else {return;}
}
reject(value) {if (this.#state !== "pending") return;
this.#result = value;
this.#state = "rejected";
}
then(onFulfilled, onRejected?) {return new MyPromise((resolve, reject) => {if (this.#state === "pending") {this.#callBackFnArray.push(() => {resolve(onFulfilled(this.#result));
});
} else if (this.#state === "fulfilled") {queueMicrotask(() => {resolve(onFulfilled(this.#result));
});
} else if (this.#state === "rejected") {onRejected(this.#result);
}
});
}
}
八. 结语
其实在我写 《js 进阶之路》专栏,从第一篇开始就感触颇深,不晓得我这种一点一点知识点碎碎的讲是否能合乎当初快节奏的浏览习惯 … 我只能站在我的观众都是小白的角度去思考每一个问题,包含之前的文章都是。
缓缓地从本人写博客中就逐步领会到,阮一峰 ,阮大, 张鑫旭,旭神等泛滥博主在写出一篇尽量能让小白去了解他们想表白出的知识点的不易和艰苦,但也正是有了他们,咱们能力防止踩更多的坑。我也是从一只小白走过去的,我也深知学习的不易,如果你感觉本篇对你有帮忙,还心愿能分享给你一起学习路上的小伙伴。
正因为淋过雨,所以想为后来者撑一把伞☔️~
也在这里祝贺本人,《手写 Promise》 完结撒花 🎉