宏任务和微任务
前言: 上一篇我们知道了 “回调地狱” 是如何产生的。并且成功引入了 Promise 的核心知识。距离 《手写 Promise》 又更近了一步,但是我们仍需要去理解更深层次的概念----- 宏任务和微任务 ,才能继续往下讲解。
我非常希望你能在阅读完前置任务后再往下阅读本文(0/3)
我的每一篇博文的出发点都是:我是在面向一个初学者来进行讲解的,所以我在阐述某些知识点的时候,不会写一些很高深的话来凸显自己的水平,都会以:“假如我是一个初学者,当时如果有人这样告诉我的话该有多好~” 这也是费曼学习法的观点。Promise 在实际开发过程中是百分百最经常使用的,所以如果你是以找工作为目的的学习,我希望你能好好的理解这几篇文章内容,真心希望大家能在我的博文中领悟到一些东西。🎁
一. 做个 demo 热热身
别着急,我们先回顾一下。之前在讲 JS代码的运行机制时,我们大概知道setTimeout
会将自身参数,也就是传入的回调函数推进一个任务队列,然后等待主线程执行完毕后再将任务队列中的代码推进主线程去执行。并且会无限循环这个过程,这个过程被我们称为 “事件循环(eventloop)” 。
下面的代码,我相信你已经知道原因了。
ok,我们现在看下一段代码。思考一下,你觉得会是?
结果如下:
如果你没感觉到差异,小伙子水平不错哦~ 👏
不理解别着急,我来解释一下原因是什么。首先 setTimout('1')
的输出肯定被放进了 任务队列 ,所以 console.log('3')
的输出结果 3 是肯定要在 1 之前,这个我相信你肯定知道原因。关键就在于为什么在 Promise 这里的 2 会在第一输出。
下面我不写箭头函数了,我害怕箭头函数会让初学者看不出来这就是一个函数而已。
没错,它真的就是一个普普通通的函数。如果你对 Class 熟悉的话,它其实就是下面的写法。
我再强调一遍,这个 “executor” 构造器函数就是一个普普通通的函数仅此而已,不是什么关键词!!!就是一个函数。当这个函数作为 Promise 类的参数时,会被在 Promise 类的 constructor 函数中马上被调用。
所以我们可以得出,传递给 Promise类 的回调函数,会被马上执行。它被并没有被推进任务队列中去。
又因为它是在 console.log(3)
之前写的,根据我们 JS 代码从上往下执行的原理,自然而然的结果就是 2->3->1
是不是觉得很简单呢?我们稍微加一点难度,看下面一段代码。
别直接想结果,你想一想控制台会是什么样子的?
奇怪?我们的 2 跑哪里了?如果你不知道这一点,我希望你回过头去看一下上一篇- 手写“回调地狱”,再来继续下面的学习。
从上一章节我们其实我们知道,我们如果要取 Promise,resolve 保存的数据,需要在 Promise 实例身上的 then
方法中去拿,由此可知我们需要按照下面的写法才能拿到数字 2。
那么问题来了,接下来的输出顺序会是?我们打印一下:
- 怎么回事呢?怎么顺序就又变了呢?🤔
二. 宏任务和微任务(MacroTask & MicroTask)
setTimout
的回调函数是会被放在任务队列的对吧?我们都知道任务队列里的任务会乖乖排队,等着主线程老大执行完再进去主线程去执行。关键点就来了!这里有个插队的!!没错,就是我们的微任务队列。
是的,在 JS 里其实存在另外一个任务队列---微任务队列(MicroTask)。我们的 setTimout
的回调函数其实被放进了一个叫宏任务队列里。为什么会产生两个队列呢?其实很好理解。
我们假设一个场景。我们去饭店吃饭。我们总是会把菜点完,然后把写着好几个菜的菜单拿给服务员,最好服务员一起送过去厨房做对吧?这时候有意思的点就来了,厨师👨🍳拿到菜单以后,一看《土豆炖牛肉》,《红烧肉》,《鸡蛋汤》。我感觉如果是一个正常的厨师师傅,都会给做菜顺序安排合理。“我不可能一看,第一行《土地炖牛肉》,后面的我压根不看,我就必须把这个菜先做了才能做后面的。”结果第一道菜50多分钟,客人干等着50分钟。
这时候就把《土地炖牛肉》开启高压锅放进了宏任务队列,转手把《鸡蛋汤》和《红烧肉》放进主线程去做。
当《土豆炖牛肉》快好的前几分钟,(注意!:这时候红烧肉和鸡蛋汤已经吃饱喝足被消灭了) 你突然又点了一个《拍黄瓜》🥜。师傅一看,《拍黄瓜》简单啊,虽然这个菜是最后上的,但是师傅直接给你放《微任务》啪啪啪几分钟给你搞好了,速度肯定比《土豆炖牛肉》剩下几分钟还要快,自然而然你就先吃到了《拍黄瓜》。
同理,在 JS 中,我们去拿一些页面相对更重要数据的时候,就是需要去“插“一些相对不那么“重要”的宏任务的队,才能保证我们页面的正确加载。而很显然,我们的 Promise 就是需要存放相对重要的数据时用得,所以它需要比 setTimeout
这个宏任务队列在事件循环的时候先执行。所以事件循环会是下面的顺序。
如果没有的话,再去询问宏任务队列
了解了这些以后,我们再回过头看一下之前的代码,就很清晰了。
首先控制台先执行同步任务 console.log('3')
,之后主线程为空。然后紧接着就去询问微任务队列,console.log('2')
,微任务队列执行完毕,最后询问宏任务队列。 console.log('1')
。
结果输出结果就如下所示: