javascript|事件循环、宏任务与微任务、Promise与 Async/Await以及常见面试题

事件循环(event loop)

  1. JS分为同步任务和异步任务,同步任务会在主线程上执行(形成执行栈,先进后出),异步任务会先放置在任务队列中(先进先出);
  2. 当主线程上的同步任务全部执行完成后,js会在任务队列中依次取出异步任务并执行。
  3. JS主线程不断的循环往复的从任务队列中读取任务,执行任务,这中运行机制遍称为事件循环。
但事件循环中的任务队列并不是唯一的,每个事件循环都有一个微任务队列以及多个宏任务队列。
宏任务(Macrotasks)与微任务(Microtasks) 【javascript|事件循环、宏任务与微任务、Promise与 Async/Await以及常见面试题】异步任务可分为 宏任务 和 微任务两类,不同的API注册的异步任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。从本质上来说,宏任务是ES6语法规定的,微任务是浏览器规定的,微任务的优先级要高于宏任务。
宏任务包含的API有:script(整体代码)、setTimeout、setInterval等
微任务包含的API有:Promise等
因此一次事件循环的大致过程为:
------------------------------------------ javascript|事件循环、宏任务与微任务、Promise与 Async/Await以及常见面试题
文章图片

Promise 与Async/Await Async/Await可看作是Promise异步执行的优化,在其之前,Promise需要多个then()来链式调用,而有了await,就不必这么繁琐。
从定义上来看, async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。而在async/await中,在出现await出现之前,其中的代码也是立即执行的。那么出现了await时候发生了什么呢?
其实,Async函数会返回一个Promise对象,Await用于等待函数的返回值,这个返回值可以是一个Promise对象,也可以是任意一个表达式的结果。
  1. 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  2. 如果它等到的是一个 Promise 对象,await 会阻塞当前路径后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
那么Promise中的代码会如何执行呢?
其实,Promise 构造函数中的第一个参数,是在 new 的时候执行,构造函数执行时,里面的参数进入执行栈执行;而后续的 .then 则会被分发到 microtask 的 Promise 队列中去。同理,在async/await中,函数从上到下依次执行,当碰到返回值时,await
会等待函数返回值并且将其放置在对应的任务队列后让出执行栈,JS向下执行其余代码,之后await得到返回值,执行await语句。
练习题 学了这么多,接下来我们用几道题巩固一下知识。
练习一
function testSometing() { console.log("执行testSometing"); return "testSometing"; }async function testAsync() { console.log("执行testAsync"); return Promise.resolve("hello async"); }async function test() { console.log("test start..."); const v1 = await testSometing(); //关键点1 console.log(v1); const v2 = await testAsync(); console.log(v2); console.log(v1, v2); }test(); var promise = new Promise((resolve)=> { console.log("promise start.."); resolve("promise"); }); //关键点2 promise.then((val)=> console.log(val)); console.log("test end...")

我们来分析一下这个代码的执行顺序:
  1. JS会从上到下执行script整体这一宏任务
  2. 先定义了一个testSomething()函数,然后定义了异步函数testAsync(),test()
  3. 执行test异步函数,打印“test start….”,此时出现await,会先打印“执行testSometing”,随后await等待返回值,并且让出执行栈。
  4. 函数接着向下执行,定义了一个promise异步函数,new的时候执行第一个参数,打印promise start…,随后Promise状态变为resolved
  5. 继续向下执行,promise.then()中的函数被放到微任务队列中
  6. 打印test end…
  7. 第一轮执行完毕,又会跳到await,此时函数返回testSometing,并打印
  8. 接着又遇到await testAsync(),打印“执行testAsync”,让出执行栈
  9. 此时,微队列中的任务promise.then()进入执行栈,打印promise
  10. 然后又跳到await处。得到返回值 hello async并打印
  11. 打印testSometing hello async
    因此,整个函数的返回结果为
test start... 执行testSometing promise start.. test end... testSometing 执行testAsync promise hello async testSometing hello async

练习二
console.log('script start'); setTimeout1(function() { console.log('timeout1'); }, 10); new Promise(resolve => { console.log('promise1'); resolve(); setTimeout2(() => console.log('timeout2'), 10); }).then(function() { console.log('then1') })console.log('script end');

执行过程:
  1. 执行宏任务script整体代码,打印script start
  2. 将setTimeout1放入宏任务队列中
  3. 遇到new,执行Promise,打印promise1,执行resolve(),Promise状态变为resolved,将.then()放入微任务队列中
  4. 将setTimeout2放入宏任务队列
  5. 打印script end
  6. 微任务队列中的任务进入执行栈,打印then1
  7. 随后微任务队列为空,这一事件循环结束
  8. 第二轮循环,执行宏任务队列中的setTimeout1,打印timeout1,没有微任务,结束循环
  9. 第三次循环,打印timeout2
    因此,代码执行结果为
script start promise1 script end then1 timeout1 timeout2

练习三(之一)
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'); }, 0)async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');

代码执行过程:
  1. 定义异步函数async1 与async2
  2. 打印script start
  3. 将setTimeout放入宏任务队列中
  4. 调用async1,打印async1 start,遇到await,紧接着输出async2,然后将await后面的代码也就是console.log(‘async1 end’)加入到微任务队列中,接着跳出async1函数来执行后面的代码。
  5. 遇到new,执行Promise,打印promise1,执行resolve(),将.then()放入微任务队列中
  6. 打印script end
  7. 微任务队列中的任务依次进入执行栈,打印async1 endpromise2
  8. 检测到没有微任务,第一次事件循环结束
  9. 第二次事件循环,执行宏任务,打印setTimeout
    因此,代码执行结果为
script start async1 start async2 promise1 script end async1 end promise2 setTimeout

练习三(之二)
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { //async2做出如下更改: new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise3'); resolve(); }).then(function() { console.log('promise4'); }); console.log('script end');

执行顺序:
  1. 打印script start
  2. 将setTimeout放入宏任务队列
  3. 执行async1(),打印async1 start,遇到await,new Promise打印promise1,执行resolve(),将.then放入微任务队列,之后将console.log(‘async1 end’)放入微任务队列,让出执行栈
  4. new Promise打印promise3,执行resolve(),将.then放入微任务队列
  5. 打印script end
  6. 依次执行微任务队列中的任务,打印promise2async1 endpromise4
  7. 第一次事件循环结束,执行下一个宏任务,打印setTimeout
    因此代码执行结果为
script start async1 start promise1 promise3 script end promise2 async1 end promise4 setTimeout

练习三(之三)
async function async1() { console.log('async1 start'); await async2(); //更改如下: setTimeout(function() { console.log('setTimeout1') },0) } async function async2() { //更改如下: setTimeout(function() { console.log('setTimeout2') },0) } console.log('script start'); setTimeout(function() { console.log('setTimeout3'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');

看了这么多,相信你已经能独立完成,因此不再赘述执行过程,只贴出结果看看你的想法对吗
注:await后的函数为setTimeout,因此会将其加入宏任务队列中
script start async1 start promise1 script end promise2 setTimeout3 setTimeout2 setTimeout1

练习三(之四)
async function a1 () { console.log('a1 start') await a2() console.log('a1 end') } async function a2 () { console.log('a2') }console.log('script start')setTimeout(() => { console.log('setTimeout') }, 0)Promise.resolve().then(() => { console.log('promise1') })a1()let promise2 = new Promise((resolve) => { resolve('promise2.then') console.log('promise2') })promise2.then((res) => { console.log(res) Promise.resolve().then(() => { console.log('promise3') }) }) console.log('script end')

结果
script start a1 start a2 promise2 script end promise1 a1 end promise2.then promise3 setTimeout

    推荐阅读