了解 JavaScript 中的异步编程:事件循环初学者指南
您有没有想过为什么某些 JavaScript 代码似乎运行不正常?理解这一点的关键是事件循环。
JavaScript 的事件循环可能很难理解,尤其是在处理不同类型的异步操作时。在本文中,我们将详细介绍 JavaScript 如何处理同步和异步代码、微任务和宏任务,以及为什么会发生某些事情按特定顺序。
目录
-
同步和异步代码
- 什么是同步代码
- 什么是异步代码
- JavaScript 中的异步模式
- 同步与异步代码
-
微任务和宏任务
- 什么是微任务
- 什么是宏任务
- 微任务与宏任务
-
事件循环
- 什么是事件循环
- 事件循环如何工作
-
示例
- 示例 1:带有 Promise 和事件循环的计时器
- 示例 2:嵌套 Promise 和计时器
- 示例 3:混合同步和异步操作
- 结论
同步和异步代码
JavaScript 以两种主要方式处理操作:同步 和 异步。理解它们之间的区别是掌握 JavaScript 如何处理任务以及如何编写高效、非阻塞代码的关键。
什么是同步代码?
JavaScript 中默认使用同步代码,这意味着每一行都按顺序依次运行。例如:
console.log("First"); console.log("Second");
这将输出:
First Second
什么是异步代码?
另一方面,异步代码允许某些任务在后台运行并稍后完成,而不会阻塞其余代码。像 setTimeout() 或 Promise 这样的函数就是异步代码的示例。
这是使用 setTimeout() 的异步代码的简单示例:
console.log("First"); setTimeout(() => { console.log("Second"); }, 0); console.log("Third");
这将输出:
First Third Second
JavaScript 中的异步模式:
JavaScript 中有几种处理异步操作的方法:
- 回调: 作为参数传递给另一个函数的函数,并在第一个函数完成其任务后执行。
代码示例:
console.log("Start"); function asyncTask(callback) { setTimeout(() => { console.log("Async task completed"); callback(); }, 2000); } asyncTask(() => { console.log("Task finished"); }); console.log("End");
- Promises: Promise 代表异步函数最终将返回的未来值(或错误)。
代码示例:
console.log("Start"); const asyncTask = new Promise((resolve) => { setTimeout(() => { console.log("Async task completed"); resolve(); }, 2000); }); asyncTask.then(() => { console.log("Task finished"); }); console.log("End");
- Async/Await: Async/await 是构建在 Promise 之上的语法糖,允许我们编写看起来同步的异步代码。
代码示例:
console.log("Start"); async function asyncTask() { await new Promise((resolve) => { setTimeout(() => { console.log("Async task completed"); resolve(); }, 2000); }); console.log("Task finished"); } asyncTask(); console.log("End");
同步与异步代码
为了更好地理解 javascript 的每种执行方法以及它们之间的差异,这里详细介绍了 javascript 函数的多个方面的差异。
Aspect | Synchronous Code | Asynchronous Code |
---|---|---|
Execution Order | Executes line by line in a sequential manner | Allows tasks to run in the background while other code continues to execute |
Performance | Can lead to performance issues if long-running tasks are involved | Better performance for I/O-bound operations; prevents UI freezing in browser environments |
Code Complexity | Generally simpler and easier to read | Can be more complex, especially with nested callbacks (callback hell) |
Memory Usage | May use more memory if waiting for long operations | Generally more memory-efficient for long-running tasks |
Scalability | Less scalable for applications with many concurrent operations | More scalable, especially for applications handling multiple simultaneous operations |
This comparison highlights the key differences between synchronous and asynchronous code, helping developers choose the appropriate approach based on their specific use case and performance requirements.
Microtasks and Macrotasks
In JavaScript, microtasks and macrotasks are two types of tasks that are queued and executed in different parts of the event loop, which determines how JavaScript handles asynchronous operations.
Microtasks and macrotasks are both queued and executed in the event loop, but they have different priorities and execution contexts. Microtasks are processed continuously until the microtask queue is empty before moving on to the next task in the macrotask queue. Macrotasks, on the other hand, are executed after the microtask queue has been emptied and before the next event loop cycle starts.
What are Microtasks
Microtasks are tasks that need to be executed after the current operation completes but before the next event loop cycle starts. Microtasks get priority over macrotasks and are processed continuously until the microtask queue is empty before moving on to the next task in the macrotask queue.
Examples of microtasks:
- Promises (when using .then() or .catch() handlers)
- MutationObserver callbacks (used to observe changes to the DOM)
- Some process.nextTick() in Node.js
Code Sample
console.log("Start"); Promise.resolve().then(() => { console.log("Microtask"); }); console.log("End");
Output:
Start End Microtask
Explanation:
- The code first logs "Start", which is synchronous.
- The promise handler (Microtask) is queued as microtask.
- The "End" is logged (synchronous), then the event loop processes the microtask, logging "Microtask".
What are Macrotasks
Macrotasks are tasks that are executed after the microtask queue has been emptied and before the next event loop cycle starts. These tasks represent operations like I/O or rendering and are usually scheduled after a certain event or after a delay.
Examples of macrotasks:
- setTimeout()
- setInterval()
- setImmediate() (in Node.js)
- I/O callbacks (file reading/writing)
- UI rendering tasks (in browsers)
Code Example:
console.log("Start"); setTimeout(() => { console.log("Macrotask"); }, 0); console.log("End");
Output:
Start End Macrotask
Explanation:
- The code first logs "Start", which is synchronous.
- The setTimeout() (macrotask) is queued.
- The "End" is logged (synchronous), then the event loop processes the macrotask, logging "Macrotask".
Microtasks vs Macrotasks
Aspect | Microtasks | Macrotasks |
---|---|---|
Execution Timing | Executed immediately after the current script, before rendering | Executed in the next event loop iteration |
Queue Priority | Higher priority, processed before macrotasks | Lower priority, processed after all microtasks are complete |
Examples | Promises, queueMicrotask(), MutationObserver | setTimeout(), setInterval(), I/O operations, UI rendering |
Use Case | For tasks that need to be executed as soon as possible without yielding to the event loop | For tasks that can be deferred or don't require immediate execution |
事件循环
事件循环是 JavaScript 中的一个基本概念,尽管 JavaScript 是单线程的,但它仍然可以实现非阻塞异步操作。它负责处理异步回调并确保 JavaScript 继续平稳运行,而不会被耗时的操作阻塞。
什么是事件循环
事件循环是一种允许 JavaScript 高效处理异步操作的机制。它不断检查调用堆栈和任务队列(或微任务队列)以确定接下来应该执行哪个函数。
为了更好地理解事件循环,了解 JavaScript 内部的工作原理非常重要。值得注意的是,JavaScript 是一种单线程语言,这意味着它一次只能做一件事。只有一个调用堆栈,它存储要执行的函数。这使得同步代码变得简单,但它给从服务器获取数据或设置超时等需要时间才能完成的任务带来了问题。如果没有事件循环,JavaScript 将陷入等待这些任务的状态,并且不会发生其他任何事情。
事件循环如何工作
1. 调用栈:
调用堆栈是保存当前正在执行的函数的地方。 JavaScript 在处理代码时会在调用堆栈中添加和删除函数。
2、异步任务启动:
当遇到像 setTimeout、fetch 或 Promise 这样的异步任务时,JavaScript 会将该任务委托给浏览器的 Web API(如 Timer API、Network API 等),后者在后台处理该任务。
3. 任务移至任务队列:
一旦异步任务完成(例如,计时器完成,或者从服务器接收到数据),回调(处理结果的函数)就会被移动到任务队列(或者在 Promise 的情况下为微任务队列) .
4. 调用堆栈完成当前执行:
JavaScript 继续执行同步代码。一旦调用堆栈为空,事件循环就会从任务队列(或微任务队列)中取出第一个任务并将其放入调用堆栈中执行。
5. 重复:
这个过程会重复。事件循环确保当前同步任务完成后处理所有异步任务。
示例
现在我们对事件循环的工作原理有了更好、更清晰的了解,让我们看一些示例来巩固我们的理解。
Example 1: Timer with Promises and Event Loop
function exampleOne() { console.log("Start"); setTimeout(() => { console.log("Timeout done"); }, 1000); Promise.resolve().then(() => { console.log("Resolved"); }); console.log("End"); } exampleOne();
Output:
Start End Resolved Timeout done
Explanation:
- Step 1: "Start" is printed (synchronous).
- Step 2: setTimeout schedules the "Timeout done" message after 1 second (macrotask queue).
- Step 3: A promise is resolved, and the "Resolved" message is pushed to the microtask queue.
- Step 4: "End" is printed (synchronous).
- Step 5: The call stack is now empty, so the microtask queue runs first, printing "Resolved".
- Step 6: After 1 second, the macrotask queue runs, printing "Timeout done".
Example 2: Nested Promises and Timers
function exampleTwo() { console.log("Start"); setTimeout(() => { console.log("Timer 1"); }, 0); Promise.resolve().then(() => { console.log("Promise 1 Resolved"); setTimeout(() => { console.log("Timer 2"); }, 0); return Promise.resolve().then(() => { console.log("Promise 2 Resolved"); }); }); console.log("End"); } exampleTwo();
Output:
Start End Promise 1 Resolved Promise 2 Resolved Timer 1 Timer 2
Explanation:
- Step 1: "Start" is printed (synchronous).
- Step 2: The first setTimeout schedules "Timer 1" to run (macrotask queue).
- Step 3: The promise resolves, and its callback is pushed to the microtask queue.
- Step 4: "End" is printed (synchronous).
-
Step 5: The microtask queue runs first:
- "Promise 1 Resolved" is printed.
- "Timer 2" is scheduled (macrotask queue).
- Another promise is resolved, and "Promise 2 Resolved" is printed.
-
Step 6: The macrotask queue is processed next:
- "Timer 1" is printed.
- "Timer 2" is printed last.
Example 3: Mixed Synchronous and Asynchronous Operations
function exampleThree() { console.log("Step 1: Synchronous"); setTimeout(() => { console.log("Step 2: Timeout 1"); }, 0); Promise.resolve().then(() => { console.log("Step 3: Promise 1 Resolved"); Promise.resolve().then(() => { console.log("Step 4: Promise 2 Resolved"); }); setTimeout(() => { console.log("Step 5: Timeout 2"); }, 0); }); setTimeout(() => { console.log( "Step 6: Immediate (using setTimeout with 0 delay as fallback)" ); }, 0); console.log("Step 7: Synchronous End"); } exampleThree();
Output:
Step 1: Synchronous Step 7: Synchronous End Step 3: Promise 1 Resolved Step 4: Promise 2 Resolved Step 2: Timeout 1 Step 6: Immediate (using setTimeout with 0 delay as fallback) Step 5: Timeout 2
Explanation:
- Step 1: "Step 1: Synchronous" is printed (synchronous).
- Step 2: The first setTimeout schedules "Step 2: Timeout 1" (macrotask queue).
- Step 3: A promise resolves, scheduling "Step 3: Promise 1 Resolved" (microtask queue).
- Step 4: Another synchronous log, "Step 7: Synchronous End", is printed.
-
Step 5: Microtask queue is processed:
- "Step 3: Promise 1 Resolved" is printed.
- "Step 4: Promise 2 Resolved" is printed (nested microtask).
-
Step 6: The macrotask queue is processed:
- "Step 2: Timeout 1" is printed.
- "Step 6: Immediate (using setTimeout with 0 delay as fallback)" is printed.
- "Step 5: Timeout 2" is printed last.
Conclusion
In JavaScript, mastering synchronous and asynchronous operations, as well as understanding the event loop and how it handles tasks, is crucial for writing efficient and performant applications.
- Synchronous functions run in sequence, blocking subsequent code until completion, while asynchronous functions (like setTimeout and promises) allow for non-blocking behavior, enabling efficient multitasking.
- Microtasks (such as promises) have higher priority than macrotasks (such as setTimeout), meaning that the event loop processes microtasks immediately after the current execution, before moving to the macrotask queue.
- The event loop is the core mechanism that allows JavaScript to handle asynchronous code by managing the execution order of tasks and ensuring that the call stack is clear before processing the next queue (microtask or macrotask).
The examples provided progressively illustrated the interaction between synchronous code, promises, timers, and the event loop. Understanding these concepts is key to mastering asynchronous programming in JavaScript, ensuring your code runs efficiently and avoids common pitfalls such as race conditions or unexpected execution orders.
Stay Updated and Connected
To ensure you don't miss any part of this series and to connect with me for more in-depth discussions on Software Development (Web, Server, Mobile or Scraping / Automation), push notifications, and other exciting tech topics, follow me on:
- GitHub
- X (Twitter)
Stay tuned and happy coding ???
以上是了解 JavaScript 中的异步编程:事件循环初学者指南的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。
