JS事件循环——宏任务和微任务

执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务(microTask)队列是否为空,如果为空的话,就执行Task(宏任务),否则就一次性执行完所有微任务。
每次单个宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。

对于面试题和日常工作而言,常用的就是对 Promise 和 async/await  的使用了,尤其是面试题,更是会用各种组合来考验你对宏任务微任务的执行顺序。

结论:

  1. 执行完一个宏任务然后会将微任务队列的所有微任务执行完,然后再去 异步定时器队列 去执行里面的定时器任务(即使定时器里有微任务,如then回调和async/await函数等)。
  2. 当Promise有多个then回调时,会把resolve里的第一个then推入微队列,然后继续执行主程序,主程序执行完,再去执行Promise的第一个then,执行完第一个then回调的所有主线程任务和宏任务后会改变Promise状态,然后继续去执行Promise之后的操作,至于在第一个then回调函数中新的微任务和之后then回调,将会添加到微队列中,等到下次读取微任务队列时去调用。可以理解为Promise的所有then回调按需添加到微队列中,这些then回调的微任务中又包含很多宏任务和微任务
  3. 定时器不受async/await 的影响,在 await 等待中有定时器调用的,其定时器同之前规则一致,即等主线程任务执行完再调用定时器队列。
  4. await函数中加入promise的then回调,await会等所有then回调执行完才会继续往下执行,如果在此遇到阻塞,则await后的操作将无法执行。
  5. 在async函数中,await之前的代码是同步执行的,即要等到await函数执行完毕,await之后的相当于then回调,推入下一次的微任务队列里。
  6. 注意,js代码预处理时,会把promise的then回调或其他微任务先添加到微任务队列,按照先进先出的顺序读取微任务队列。

例1:

                                                           

  

例2:

结果:start   end   resolve   timeout

注意:定时器里的操作都是等主线程的任务执行后再执行的(包含在定时器里的then回调函数)

例3:

注意:当Promise有多个then回调时,执行完第一个then回调的所有主线程任务和宏任务后会改变Promise状态,至于在第一个then回调函数中新的微任务和之后then回调,将会添加到微队列中,等到下次读取微任务队列时去调用。如下例4~例7

例4:

在此处,执行输出 “内部第一个then”后,此Promise作为状态已改变,其外部第一个then中的所有主线程任务已全部执行完,执行输出“外部第二个then”,至于“内部第二个then”,会在输出“内部第一个then”后推入微队列。

源代码

new Promise((resolve,reject)=>{
	console.log("外部promise");
	resolve();
}).then(()=>{

	console.log("外部第一个then");

	new Promise((resolve,reject)=>{
		console.log("内部promise");
		resolve(60);
    }).then((data)=>{
		console.log("内部第一个then",data);
    }).then(()=>{
		console.log("内部第二个then");
    });

}).then(()=>{
	console.log("外部第二个then");
})
console.log("最下面");

例5:

例6:

例7:

注意:定时器不受async/await 的影响,在 await 等待中有定时器调用的,其定时器同之前规则一致。

例8:

//结论一
//当执行 then 方法时,如果前面的 promise 已经是 resolved 状态,则直接将回调放入微任务队列中

//结论二
//当一个 promise 被 resolve 时,会遍历之前通过 then 给这个 promise 注册的所有回调,将它们依次放入微任务队列中

//结论三
//对于 then 方法返回的 promise 它是没有 resolve 函数的,取而代之只要 then 中回调的代码执行完毕并获得同步返回值,这个 then 返回的 promise 就算被 resolve

图中步骤是错的,比如步骤2产生的异步宏任务,其实就是直接压进去,不是等到主线程空了才压,顺序应该改一下。

JavaScript处理同步异步任务的方式是:用栈和队列调度分配任务,代码首次运行主线程按照同步任务顺序,依次执行,执行期间产生的异步宏任务则挂起不执行,挂起的方式是添加至宏任务执行队列,产生的微任务也挂起,方式是添加至微任务执行队列。待所有同步任务执行完成,主线程从宏任务执行队列取出最先添加进宏任务执行队列的任务,放入执行栈执行,执行完后立即去执行所有的微任务,微任务清空后再执行下一个宏任务,每执行完一个宏任务就去清空一下微任务,直到两个执行队列全清空。如此循环就是Event Loop事件循环。

对例8的await函数中加入promise的then回调,await会等所有then回调执行完才会继续往下执行,如果在此遇到阻塞,则await后的操作将无法执行。

例9:

但是,async/await 没那么简单,由以下例子得出,在async函数中,await之前的代码是同步执行的,即要等到await函数执行完毕,await之后的相当于then回调,推入下一次的微任务队列里。

例10

难点:

例11:

首先按顺序下来,把promise的then回调放进微任务队列(4),然后执行async1的函数,先输出1,然后执行await的async2函数输出2,在async的await后面的输出3被当成微任务推入微任务队列,此时微任务队列已有promise的then回调,此时async1执行完毕,输出最下面的6,然后主线程的宏任务执行完毕,去读取微任务队列,顺序输出插入的4和3,微任务队列执行完毕,读取定时器队列,输出5,程序执行完毕。

例12:

参考视频:前端面试视频教程全集(26P)| 5 小时从入门到精通_哔哩哔哩_bilibili

微任务、宏任务与Event-Loop

Promise 链式调用顺序引发的思考

图解JavaScript事件循环、执行栈、任务队列、宏任务、微任务

这一次,彻底弄懂 JavaScript 执行机制

简明实例文章

### JavaScript 任务任务的区别 在 JavaScript 中,任务任务的主要区别在于它们被调度的方式及其执行时机。 #### 任务 (Macro Task) 任务是指那些会被推入到全局的任务队列中的操作。每当一个任务执行完成之后,在下一个任务开始之前会先处理所有的任务。常见的任务包括但不限于 `setTimeout`、`setInterval` I/O 操作等[^1]。 #### 任务 (Micro Task) 相比之下,任务则是在当前正在运行的任务结束后立即得到执行的机会,直到所有可得的任务都被处理完毕才会继续下一个任务。典型的任务有 `Promise.then()` 方法回调以及由 `async/await` 构造出来的异步函数内部代码块[^2]。 ### 执行顺序 当一段脚本启动时,JavaScript 引擎首先会在调用栈中依次执行同步任务;一旦遇到任何类型的异步任务,则根据其性质分别加入相应的任务队列——即任务队列或任务队列。具体来说: - 当前任务内的所有同步代码被执行; - 接着检查并清空任务队列里的每一项直至为空; - 此过程重复进行,每次从任务队列取出一项作为新的上下文环境下的第一个待办事项,并在其前后都给予任务优先级较高的待遇[^3]。 为了更直观地展示这一流程,下面给出了一段简单的示例代码来观察任务任务之间的交互行为: ```javascript console.log('Script start'); setTimeout(() => { console.log('Timeout'); }, 0); new Promise((resolve) => { resolve(); }).then(() => { console.log('Promise resolved'); }); console.log('Script end'); ``` 这段程序将会按照如下顺序打印日志消息: 1. Script start 2. Script end 3. Promise resolved 4. Timeout 这是因为尽管设置了延迟时间为零秒 (`0`) 的计时器(`setTimeout`),但由于它是属于任务范畴内的一项工作,所以即便设定的时间已经到达也必须等到本轮事件循环周期结束且所有现存任务完成后才能真正获得执行机会。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值