JavaScript的任务跟微任务

缘由

前端团队在执行 code review 时候,我们发现早期的代码中有很多滥用了 async await 的代码。虽然在执行中,虽然在同步的代码中乱写 async await 并没有导致明显的bug,但加了await跟不加await的结果是否会有区别?今天我们先看一下以下几行示例代码的执行结果

async function fn() {
  console.log('fn start')
  await fn2()
  console.log('fn end')
}

function fn2() {
  console.log('fn2')
}

console.log('script start')
fn()
console.log('script end')

结果对比

  • 加await输出的结果是 script start -> fn start -> 2 -> script end -> fn end

  • 不加await输出结果是 script start -> fn start -> 2 -> fn end -> script end

在文章开始之前,本文我所说的任务,即为宏任务。只是我个人更倾向于mdn对其的称呼,是“任务”而不是“宏任务”,具体后面会解释

题目1

  1. 想一想,如果我们执行 foo ,是否会报栈溢出的错误?为什么?
function foo() { 
  setTimeout(foo, 0)
}

题目1 答案

不会

事件循环 Eventloop

JavaScript 并发模型基于“事件循环”,浏览器提供了运行时环境来执行我们的 JavaScript 代码。主要组成包括调用栈、事件循环、任务队列和 Web API 等。JavaScript 环境的可视化表示大致如下所示。(Promise应该是js的内置方法而不是WebAPIs)

image.png

JavaScript调用栈是后进先出的,引擎每次从调用栈中取出一个函数,然后按顺序从上到下运行代码。每当遇到一些异步代码的时候,比如setTimeout,他会将其丢给WebAPI,也就是箭头指向的1。

WebAPI会等到合适的时机,再把回调函数,发送到任务队列,也就是箭头2的操作。

Eventloop会不断监视调用栈是否为空,等到调用栈的任务为空的时候,他就会取出回调函数放入调用栈中。(在调用栈不为空的情况下,eventloop不会把任何回调放到调用栈中去,因此也解释了setTimeout的时间不一定是准确的)

从事件循环机制来看这个方法的执行顺序如下:

  1. 调用 foo()将把 foo 函数放到调用栈中
  1. 在处理内部代码时, JS 引擎遇到 setTimeout
  1. 将回调函数foo交给 webAPI,函数执行结束,出栈;调用栈又是空的
  1. 由于定时器设置为 0,时间一到回调函数 foo 将被发送到任务队列
  1. 进程再次重复,调用栈永远不会溢出

题目2

想一想,如果我们执行下方的foo,再点击页面的按钮,会有响应吗?为什么?

function foo() {
  return Promise.resolve().then(foo)
}

题目2答案

不会有响应

虽然他们都是异步,但跟setTimeout不一样,Promise.then是微任务。他们很相像,但是执行时机上是不一样的。

每当一个任务退出且执行上下文堆栈为空时,将逐个执行微任务队列中的每个微任务,与任务的不同之处在于,微任务的执行会一直持续到微任务队列为空——即使在此期间计划了新任务。换句话说,微任务可以将新的微任务编入队列,这些微任务将在下一个任务开始运行之前和当前事件循环迭代结束之前执行。

什么时候会产生任务

mdn上给了我们答案

  • A new JavaScript program or subprogram is executed (such as from a console, or by running the code in a [

你可能感兴趣的:(JavaScript的任务跟微任务)