一、同步任务与异步任务
JavaScript是一门单线程语言,但是单线程并不意味着阻塞。实现单线程非阻塞的方式就是事件循环机制。在JavaScript中,所有的事件都可以分为同步任务和异步任务。
同步任务:立即执行的任务。同步任务一般会直接进入到主线程执行。
异步任务:异步执行的任务,比如ajax请求、setTimeout定时器等。
任务进入执行栈,会先判断当前任务是同步任务还是异步任务,如果是同步任务则会进入到主线程,立即执行;异步任务会先放到Event Table,注册回调函数到Event Queue。等待所有的同步任务执行完后,主线程会去Event Queue中读取异步任务到主线程中执行,这个过程的不断重复就是事件循环。
二、微任务与宏任务
异步任务分为微任务和宏任务:
微任务:一个需要异步执行的函数,执行时机是在主函数结束之后,当前宏任务结束之前。常见的微任务有:Promise.then、MutationObserver、Object.observe(已废弃,被Proxy对象替代)、process.nextTick
宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合。常见的宏任务有:script(外层同步代码)、setTimeout/setInterval、UI rendering/UI事件、postMessage、MessageChannel、setImmediate、I/O(Node.js)
微任务和宏任务的执行机制:
执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中;当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完。
例题:
console.log(1)①
setTimeout(()=>{
console.log(2)②
},0)
new Promise((resolve,reject)=>{
console.log('new Promise')③
resolve()
}).then(()=>{
console.log('then')④
})
console.log(3)⑤
执行过程为:
(1)①是同步任务,直接打印1,
(2)②为宏任务,
(3)③是同步任务,直接打印'new Promise',
(4)④是微任务,放入微任务队列,后面再执行
(5)⑤是同步任务,直接打印3.
(6)本轮宏任务执行完毕,现在去微任务列表查看是否有微任务,④是微任务,执行打印'then'
(7)一次宏任务执行完,再去执行新的宏任务,即定时器宏任务,打印2
所以最终打印的结果是1-->new Promise-->3-->then-->2
三、async与await
async用于声明异步函数,await用于等待异步任务执行
async函数返回一个promise对象,await命令后面是一个Promise对象,返回该对象的结果。如果不是Promise对象,直接返回对应的值,不管await后面跟着什么,都会阻塞后面的代码!!
await的执行机制:
await fn():会立即执行fn(),但是会阻塞fn()后面的代码(加入微任务队列)
例题:
async function async1() {
console.log('async1 start')①
await async2()②
console.log('async1 end')③
}
async function async2() {
console.log('async2')④
}
console.log('script start')⑤
setTimeout(function () {
console.log('settimeout')⑥
})
async1()⑦
new Promise(function (resolve) {
console.log('promise1')⑧
resolve()
}).then(function () {
console.log('promise2')⑨
})
console.log('script end')⑩
第一轮循环:先执行⑤,打印出:script start,setTimeout为宏任务,放入宏任务队列;执行⑦,进入async1(),先打印:async1 start,遇到await会阻塞后面的语句执行,所以将③放入微任务队列,立即执行②,打印出async2;Promise.then()为微任务,⑨放入微任务队列,Promise立即执行打印:promise1,接着向下执行打印script end
宏任务:setTimeout
微任务:③、⑨
script start-->async1 start-->async2-->promise1-->script end-->async1 end-->promise2-->settimeout
第一轮宏任务结束,查看微任务队列,执行所有的微任务③⑨,打印promise2,接着下一轮宏任务。
执行setTimeout,打印settimeout。
第二轮宏任务结束,没有要执行的微任务队列,执行③,打印async1 end
最终打印的结果为script start-->async1 start-->async2-->promise1-->script end-->promise2-->settimeout-->async1 end