第 42 期 - JavaScript 的 async/await、事件循环与 Promise 面试题解析
摘要
本文先阐述 async/await 与普通函数的区别,包括返回值和异常处理,再介绍操作系统的进程与线程概念,然后讲述浏览器中的 JavaScript 线程和事件循环,区分宏任务与微任务队列,最后通过 Promise 执行面试题巩固知识,并提及 Node 事件循环的相关内容。
一、async/await 相关内容
1. async 函数与普通函数的区别
- 函数声明方式:async 函数有三种基础写法,分别是
async function、箭头函数前加async、类中的方法加async。它和生成器函数写法有相似之处,但async位置不同。 - 执行方式:
- 起初推测
async函数要么无法正常执行,要么函数体内内容异步,但实际代码测试发现和普通函数调用一样,函数体内容按顺序执行。 - 返回值区别:
async函数返回值是一个封装后的Promise,这是与普通函数的重要区别之一。- 当返回普通值时,相当于被包裹到
Promise.resolve中;返回Promise时,状态由Promise决定;返回对象且实现了thenable,由对象的then方法决定。
- 异常处理区别:普通函数抛出异常直接输出堆栈跟踪错误信息且不再执行,而
async异步函数异常会进行Promise.reject(error)操作,可被Promise的catch方法捕获。
- 起初推测
2. async 函数中的关键字 await
- 作用及特性:
await只能在async异步函数内使用,其功能类似生成器函数中的yield,意为等待异步操作(通常是Promise)完成。它会暂停函数执行,直到Promise变为fulfilled状态才继续,并且直接将Promise的结果作为返回值,形成同步代码视觉效果。 - 返回值情况:
await后面可跟任意值、异步的Promise或者Thenable对象。若Promise正常返回则获取处理结果,若为reject状态则作为函数Promise的reject值;若等待的值不满足thenable,则返回表达式本身的值。 - 具体示例:如
async函数中的await后面跟网络请求(返回Promise),以及跟普通值、Thenable对象等不同情况的示例展示了其返回值特点。
二、操作系统的进程与线程概念
1. 进程与线程的基本概念
- 进程:计算机已运行的程序,是操作系统管理程序的一种方式,启动一个应用程序可能会启动一个或多个进程。
- 线程:操作系统能够运行运算调度的最小单位,通常包含在进程中。进程像是线程的容器,每个进程至少有一个线程(主线程)来执行程序代码。
- 二者区别:进程负责资源初步分配,资源都在进程身上,所以进程较重,切换开销大;线程基本不拥有资源,只具备运行所需的部分,与同一进程下的其他线程共享进程资源,所以线程很轻,切换开销小。
2. 操作系统的工作方式
- 操作系统通过 CPU 在多个进程间快速切换,让多个进程看似同时工作。当进程中的线程获取到时间片(CPU 资源运行时间)时,就可以执行代码,用户感受不到这种切换。
三、浏览器中的 JavaScript 线程和事件循环
1. 浏览器中的 JavaScript 线程
- 多数浏览器是多进程的,打开一个 tab 页面会开启一个新进程,每个进程有很多线程,其中包括执行 JavaScript 代码的线程。JavaScript 代码在单独线程执行,同一时刻只能做一件事,耗时操作不由 JavaScript 线程执行(如网络请求、定时器等由浏览器其他线程完成),JavaScript 线程专注于代码运行。
2. 事件循环
- 认识事件循环:
- 完整流程包括执行代码脚本、栈操作(同步代码)、推送耗时操作给浏览器其余线程、推送到事件队列、任务处理(宏微任务),并且 4 与 5 循环重复以保持应用持续响应外部事件。
- 例如
setTimeout函数本身是同步函数,其回调函数是异步的,会被传给浏览器其他线程计时,计时结束后交回给 JS 线程执行。
- 宏任务与微任务:
- 队列区分:事件队列可细分为宏任务队列(如
ajax、setTimeout、setInterval、DOM监听、UI Rendering等)和微任务队列(如Promise的then回调、Mutation Observer API、queueMicrotask()等)。 - 区分意义:细分为两个队列可优先处理更紧急操作,提高应用响应性避免界面阻塞。宏任务执行前要确保微任务队列已清空。
- 队列区分:事件队列可细分为宏任务队列(如
四、Promise 执行面试题
1. 面试题一
- 存在多种类型代码(定时器、
Promise、queueMicrotask、main script代码)交缠的情况,根据代码类型确定优先度执行顺序为同步代码执行、微任务执行、宏任务执行。 - 其中定时器是宏任务,
Promise的then方法和queueMicrotask是微任务,main script是同步代码。并且对于复杂的宏任务内嵌套微任务的情况,要遵循宏微任务基本规范(执行宏任务之前,所有微任务必须清空)来确定执行顺序。
2. 面试题二
- 涉及
async/await的题目,async函数中await之外的内容为同步代码,await返回的结果是Promise(归属于微任务队列)。 - 根据执行顺序进行基础排序,同步代码先执行,然后是微任务代码,最后是宏任务代码。要注意
await在async函数中的暂停效果以及后续代码的归属队列情况。
3. 面试题三
- 本题考察对
Promise状态调度的熟悉程度,存在多种容易出错的情况。 - 看似简单按顺序排列的结果是错误的,需要深入分析
Promise链的执行情况,如Promise.resolve的简写形式对执行顺序的影响,返回不同类型值(普通值、thenable、Promise)时then方法的执行顺序变化,这涉及到微任务队列的特性以及同步代码与微任务的交互情况。
4. 宏任务执行顺序示例
- 以两个
script标签中的宏任务和定时器宏任务为例,宏任务会先整体 push 到宏任务事件队列中,执行顺序不是按照简单的嵌套顺序,而是有其特定的规则,体现了宏任务与微任务的不同特点。
五、Node 事件循环
1. Node 事件循环的阶段
- 在 Node 进程中,
JS线程执行JS代码,耗时操作交给 Node 中的其他线程,结束后加入队列,JS引擎从队列获取内容。 - 一次完整的事件循环
Tick分成多个阶段,包括定时器、待定回调、idle和prepare、轮询、检测、关闭的回调函数等阶段,JS线程挨个在队列中找内容执行,执行完所有队列后一次Tick结束并不断循环。
2. Node 的宏任务与微任务
- Node 的事件循环也分微任务和宏任务,宏任务包含
setTimeout、setInterval、IO事件、setImmediate、close事件等;微任务包含Promise的then回调、process.nextTick、queueMicrotask。 - Node 中的事件循环不只是微任务队列和宏任务队列,还包含不同功能的多个队列用于处理不同类型的任务。
扩展阅读
Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有
