js的异步执行以及宏任务和微任务

41 阅读2分钟

面试题

记得有看过一道有关输出的面试题:

setTimeout(()=>console.log("d"), 0) 
var r = new Promise( function(resolve, reject){resolve()}); 
r.then(() => { 
    var begin = Date.now(); 
    while(Date.now() - begin < 1000); 
    console.log("c1") 
    new Promise(function(resolve, reject){ resolve() })
    .then(() => console.log("c2")) 
});

如果有一定js基础可以很轻松的给出 "c1", "c2", "d" 的答案顺序。因为setTimeout是宏任务,而promise是微任务,遇到promise的延迟会放入微任务中,微任务执行完执行宏任务。那么什么是宏任务,什么是微任务呢?

宏任务和微任务

上小学五年级时老师告诉我,JS是一种解释型语言,你写的每一行代码都会放入到JS引擎中执行。那么JS引擎通常在哪里呢?最常见的有浏览器,或者Node环境。而这个所处的环境就是宏任务发起的地方,setTimeout通常是宿主环境调用的JS引擎的一个Api(node环境中是timer模块)。

而promise作为一个内置对象,调用promise时是通过 new promise实例的方法使用,这种在js引擎中调用的过程叫做微任务。

为避免出现死锁的形式,js被设计为一个单线程语言。因此在js线程中,宏任务的调用会放到已有任务队列的后面,按照宏任务队列依次执行。js中宏任务和微任务的执行逻辑如下:

image.png

await函数

在ES8的版本新增了两个关键字 await async,依靠这两个关键字可以做到实现异步函数的功能。但底层原理依然是promise。

在promise中 promise内部的函数是同步的,异步只发生在 then的内容中。await/async一样,在async函数中await前的内容是同步的,await后的内容是异步的。

具体可看下面这个红绿灯的例子:

let redLight = document.querySelector('.red-light')
let yellowLight = document.querySelector('.yellow-light')
let greenLight = document.querySelector('.green-light')
function sleep(time){
  return new Promise(res => { setTimeout(res,time)})
}

function changeLight(nowLight, nextLight){
  console.log('in change light');
  nowLight.style.backgroundColor = 'white'
  if(nowLight.classList.contains('red-light') && nextLight.classList.contains('yellow-light')){
    nextLight.style.backgroundColor = 'yellow'
  } else if(nowLight.classList.contains('yellow-light') && nextLight.classList.contains('green-light')) {
    nextLight.style.backgroundColor = 'green'
  } else if(nowLight.classList.contains('green-light') && nextLight.classList.contains('red-light')) {
    nextLight.style.backgroundColor = 'red'
  } else {
    throw('error combining')
  }
}
async function main() {
  while(1){
    changeLight(yellowLight, greenLight)
    await sleep(3000)
    changeLight(greenLight, redLight)
    await sleep(1000)
    changeLight(redLight, yellowLight)
    await sleep(2000)
   }  
}
main()

使用sleep函数可以将同步的函数改为异步执行的效果。