# 任务队列

JS 是单线程的,这就意味着所有的任务需要排队执行,这就是任务队列。

# 同步任务、异步任务

为了防止主线程阻塞,JS 又分为同步任务和异步任务。

同步任务指的就是那些按顺序执行的任务。同步任务会直接进入主线程执行,只有前面一个任务执行完才会执行下一个任务。

异步任务指的就是那些需要等待的任务。异步任务会在Event Table中注册相应的回调函数,当指定的步骤完成了,Event Table会将注册了的回调函数推送到Event Queue。当主线程内的任务执行完毕为空之后,就会到Event Queue读取相应的函数,进入主线程执行。

# 宏任务、微任务

JS 是单线程的,为了更好的处理高优先级的任务,防止阻塞,需要将任务划分为宏任务和微任务。

一般宿主环境提供的异步方法都是宏任务,常见的宏任务有:

# 浏览器 Node
I/O
script代码块
setTimeout
setInterval
setImmediate
requestAnimationFrame

常见的微任务有:

# 浏览器 Node
process.nextTick
MutationObserver
Promise.then catch finally

# 事件循环

执行完成后一个宏任务后,会把当前的微任务队列全部执行完毕,然后执行GUI渲染,再从宏任务队列中取出一个宏任务,放到执行栈中执行。这个宏任务执行完毕后,再去清空微任务,依次循环,直到所有代码执行完。

# 微任务和GUI渲染

<script>
  document.body.style.background = 'red';
  console.log(1)
  Promise.resolve().then(()=>{
      console.log(2)
      document.body.style.background = 'yellow';
  })
  console.log(3);
</script>

上述代码执行结果为

1
3
2

页面最终渲染为黄色,并且不会显示绿色

但是如果把代码改造如下

<script>
    document.body.style.background = 'red';
    console.log(1)
    setTimeout(() => {
      console.log(2)
      document.body.style.background = 'yellow';
    }, 1000)
    console.log(3);
</script>

输出结果为

1
3
2

页面先显示为红色,隔一秒后会显示成绿色

# 事件任务

<script>
  button.addEventListener('click',()=>{
    console.log('listener1');
    Promise.resolve().then(()=>console.log('micro task1'))
  })
  button.addEventListener('click',()=>{
    console.log('listener2');
    Promise.resolve().then(()=>console.log('micro task2'))
  })
  button.click(); // click1() click2()
</script>

输出结果

listener1
listener2
micro task1
micro task2

由于click不需要用户点击,click事件是立即执行的,相当于不用放到宏任务队列里,上述代码可以简化如下:

console.log('listener1');
Promise.resolve().then(()=>console.log('micro task1'))
console.log('listener2');
Promise.resolve().then(()=>console.log('micro task2'))

# 定时器任务

<script>
  Promise.resolve().then(() => {
    console.log('Promise1')
    setTimeout(() => {
        console.log('setTimeout2')
    }, 0);
  })
  setTimeout(() => {
    console.log('setTimeout1');
    Promise.resolve().then(() => {
        console.log('Promise2')
    })
  }, 0);
</script>

输出结果

Promise1
setTimeout1
Promise2
setTimeout2

# 实战题

async function async1 () {
  console.log('async1 start');
  /*
  下面两句相当于下面这样
  await async2().then(console.log('async1 end'))
  */
  await async2();
  console.log('async1 end')

  /* 
  在node环境下运行的结果是不同的,因为浏览器可以识别出async2是个promsie,但node识别不出来,所以会用一个promise包装下ansync2(),如果内部发现resolve的值是一个promise,就会调用这个promise的then方法,当这个promise完成后,后面还有一个then

  new Promise((resolve, reject) => resolve(async2())).then(() => {
    console.log('async1 end')
  })
  */
}

async function async2 () {
  console.log('async2')
}
console.log('script start')

setTimeout(() => {
	console.log('setTimeout');
}, 0);

async1()

new Promise((resolve) => {
  console.log('promise1');
  resolve()
}).then(()=>console.log('promise2'))

console.log('script end')

输出结果

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

下面我们再来一道练手题:

console.log(1);
async function async () {
  console.log(2);
  await console.log(3);
  console.log(4)
}
setTimeout(() => {
	console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
  console.log(6);
  resolve(7)
})
promise.then(res => {
	console.log(res)
})
async (); 
console.log(8);

输出结果

1,6,2,3, 8, 7, 4, 5

你们把上面的题做一个小小的修改来看一下它的输出结果。




 
 















console.log(1);
async function async () {
  console.log(2);
  const a = await 3;
  console.log(a)
  console.log(4)
}
setTimeout(() => {
	console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
  console.log(6);
  resolve(7)
})
promise.then(res => {
	console.log(res)
})
async (); 
console.log(8);

输出结果

1,6,2,8,7,3,4,5

async返回的是一个promise,内部实现原理是generator+co库

await 相当于yield,如果产出的是一个promise会调用这个promise的then方法

参考:

JS事件循环机制(event loop)之宏任务/微任务 (opens new window)

微任务、宏任务与Event-Loop (opens new window)