”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 了解 JavaScript 中的异步编程:事件循环初学者指南

了解 JavaScript 中的异步编程:事件循环初学者指南

发布于2024-09-15
浏览:262

Understanding Asynchronous Programming in JavaScript: Beginner

Have you ever wondered why some pieces of JavaScript code seem to run out of order? The key to understanding this is the event loop.

JavaScript's event loop can be tricky to understand, especially when dealing with different types of asynchronous operations. In this article, we'll break down how JavaScript handles synchronous and asynchronous code, microtasks and macrotasks, and why certain things happen in a specific order.

Table of Contents

  1. Synchronous and Asynchronous Codes
    • What are Synchronous Code
    • What are Asynchronous Code
    • Asynchronous Patterns in JavaScript
    • Synchronous vs Asynchronous Code
  2. Microtasks and Macrotasks
    • What are Microtasks
    • What are Macrotasks
    • Microtasks vs Macrotasks
  3. The Event Loop
    • What is the Event Loop
    • How the Event Loop Works
  4. Examples
    • Example 1: Timer with Promises and Event Loop
    • Example 2: Nested Promises and Timers
    • Example 3: Mixed Synchronous and Asynchronous Operations
  5. Conclusion

Synchronous and Asynchronous Codes

JavaScript handles operations in two main ways: synchronous and asynchronous. Understanding the difference between them is key to grasping how JavaScript handles tasks and how to write efficient and non-blocking code.

What are Synchronous Code?

Synchronous code is the default in JavaScript, meaning each line runs one after another in sequence. For example:

console.log("First");
console.log("Second");

This will output:

First
Second

What are Asynchronous Code?

Asynchronous code on the other hand allows certain tasks to run in the background and complete later, without blocking the rest of the code. Functions like setTimeout() or Promise are examples of asynchronous code.

Here's a simple example of asynchronous code using setTimeout():

console.log("First");

setTimeout(() => {
  console.log("Second");
}, 0);

console.log("Third");

This will output:

First
Third
Second

Asynchronous Patterns in JavaScript:

There are a few ways to handle asynchronous operations in JavaScript:

  1. Callbacks: A function passed as an argument to another function, and executed after the first function has completed its task.

Code Sample:

console.log("Start");

function asyncTask(callback) {
  setTimeout(() => {
    console.log("Async task completed");
    callback();
  }, 2000);
}

asyncTask(() => {
  console.log("Task finished");
});

console.log("End");
  1. Promises: A promise represents a future value (or error) that will eventually be returned by the asynchronous function.

Code Sample:

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");
  1. Async/Await: Async/await is syntactic sugar built on top of promises, allowing us to write asynchronous code that looks synchronous.

Code Sample:

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");

Synchronous vs Asynchronous Code

To better understand each of these method of execution of javascript and how they differs from each either, here is an elaborate differences across multiple aspect of javascript functions.

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

The Event Loop

The event loop is a fundamental concept in JavaScript that enables non-blocking asynchronous operations despite JavaScript being single-threaded. It's responsible for handling asynchronous callbacks and ensuring that JavaScript continues to run smoothly without getting blocked by time-consuming operations.

What is the Event Loop

The event loop is a mechanism that allows JavaScript to handle asynchronous operations efficiently. It continuously checks the call stack and the task queue (or microtask queue) to determine which function should be executed next.

To understand the event loop better, it's important to know how JavaScript works internally. It is important to note that JavaScript is a single-threaded language, meaning it can only do one thing at a time. There's only one call stack, which stores the functions to be executed. This makes synchronous code straightforward, but it poses a problem for tasks like fetching data from a server or setting a timeout, which take time to complete. Without the event loop, JavaScript would be stuck waiting for these tasks, and nothing else would happen.

How the Event Loop Works

1. Call Stack:

The call stack is where the function currently being executed is kept. JavaScript adds and removes functions from the call stack as it processes code.

2. Asynchronous Task Starts:

When an asynchronous task like setTimeout, fetch, or Promise is encountered, JavaScript delegates that task to the browser's Web APIs (like Timer API, Network API, etc.), which handle the task in the background.

3. Task Moves to the Task Queue:

Once the asynchronous task completes (e.g., the timer finishes, or data is received from the server), the callback (the function to handle the result) is moved to the task queue (or microtask queue in the case of promises).

4. Call Stack Finishes Current Execution:

JavaScript continues executing the synchronous code. Once the call stack is empty, the event loop picks up the first task from the task queue (or microtask queue) and places it on the call stack for execution.

5. Repeat:

This process repeats. The event loop ensures that all the asynchronous tasks are handled after the current synchronous tasks are done.

Examples

Now that we a better and clearer understanding of how the event loop works, let's look at some examples to solidify our understanding.

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
  • Linkedin
  • X (Twitter)

Stay tuned and happy coding ?‍??

版本声明 本文转载于:https://dev.to/emmanuelayinde/understanding-asynchronous-programming-in-javascript-synchronous-asynchronous-microtasks-macrotasks-and-the-event-loop-h5e?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何覆盖 PHP 的 `mail()` 函数中的信封返回地址?
    如何覆盖 PHP 的 `mail()` 函数中的信封返回地址?
    如何在 PHP Mail 中覆盖信封返回地址为了解决使用 PHP 的 mail() 函数设置信封返回地址的问题,这个答案提供了一个简单的解决方案。mail() 函数接受可选的第四个和第五个参数。虽然第四个参数用于设置标头,但第五个参数可用于将选项直接传递给底层的 sendmail 命令。通过在第五个...
    编程 发布于2024-11-07
  • 科技观察 #1
    科技观察 #1
    大家好,这是我上周的技术手表,其中包含很多 #react、一点 #html、一些 #css 和 #npm。 ? https://www.totaltypescript.com/how-to-create-an-npm-package 如何创建 NPM 包 创建、测试和发布 NPM 包(从初始化到发布...
    编程 发布于2024-11-07
  • mysqli_fetch_array() 何时显示错误“期望参数 1 为 mysqli_result,给定布尔值”?
    mysqli_fetch_array() 何时显示错误“期望参数 1 为 mysqli_result,给定布尔值”?
    mysqli_fetch_array() 期望 MySQLi 结果,而不是布尔值在给定的 PHP 代码中,错误“mysqli_fetch_array() 期望参数 1 为mysqli_result, boolean Give" 表示使用 mysqli_query() 的查询执行失败,它返回...
    编程 发布于2024-11-07
  • 子集和问题的 PHP 程序
    子集和问题的 PHP 程序
    子集和问题是计算机科学和动态规划中的经典问题。给定一组正整数和一个目标和,任务是确定是否存在给定集合的子集,其元素之和等于目标和。 子集和问题的PHP程序 使用递归解决方案 例子 <?php // A recursive solution for the subset sum problem ...
    编程 发布于2024-11-07
  • JavaScript 数组方法:综合指南
    JavaScript 数组方法:综合指南
    数组是 JavaScript 中最基本的数据结构之一。使用数组,您可以在单个变量中存储多个值。 JavaScript 提供了许多内置方法来操作数组,使它们具有令人难以置信的通用性。在这篇文章中,我们将探讨所有内置数组方法以及如何在 JavaScript 项目中有效地使用它们。 核心方...
    编程 发布于2024-11-07
  • 高级 T:依赖参数、推断联合以及 Twitter 上的健康交互。
    高级 T:依赖参数、推断联合以及 Twitter 上的健康交互。
    每次我用 TypeScript 写成 Foo 时,我都会感受到失败的沉重。 在一种情况下,这种感觉特别强烈:当函数采用的参数取决于哪个 "mode" 处于活动状态时。 通过一些示例代码更清晰: type Provider = "PROVIDER A" | "PR...
    编程 发布于2024-11-07
  • 如何创建人力资源管理解决方案
    如何创建人力资源管理解决方案
    1. Understanding the Basics of Frappe and ERPNext Task 1: Install Frappe and ERPNext Goal: Get a local or cloud-based instance of ERP...
    编程 发布于2024-11-07
  • 从周五黑客到发布:对创建和发布开源项目的思考
    从周五黑客到发布:对创建和发布开源项目的思考
    从周五补丁破解到发布:对创建和发布开源项目的思考 这是针对初学者和中级开发人员的系列的一部分,通过将他们的想法作为开源项目发布或引起兴趣。 这些想法是有偏见的和个人的。计划发布更多文章。通过分享一些思考,我希望能启发你做自己的项目 思考(此) 作为 Java 开发人员学习 Go l...
    编程 发布于2024-11-07
  • 可以使用 constexpr 在编译时确定字符串长度吗?
    可以使用 constexpr 在编译时确定字符串长度吗?
    常量表达式优化:可以在编译时确定字符串长度吗?在优化代码的过程中,开发人员尝试计算使用递归函数在编译时计算字符串文字的长度。此函数逐字符计算字符串并返回长度。初始观察:该函数似乎按预期工作,在运行时返回正确的长度并生成表明计算发生在编译时的汇编代码。这就提出了一个问题:是否保证length函数会在编...
    编程 发布于2024-11-07
  • 在 Raspberry Pi 上运行 Discord 机器人
    在 Raspberry Pi 上运行 Discord 机器人
    Unsplash 上 Daniel Tafjord 的封面照片 我最近完成了一个软件工程训练营,开始研究 LeetCode 的简单问题,并觉得如果我每天都有解决问题的提醒,这将有助于让我负起责任。我决定使用按 24 小时计划运行的不和谐机器人(当然是在我值得信赖的树莓派上)来实现此操作,该机器人将执...
    编程 发布于2024-11-07
  • 解锁 JavaScript 的隐藏宝石:未充分利用的功能可提高代码质量和性能
    解锁 JavaScript 的隐藏宝石:未充分利用的功能可提高代码质量和性能
    In the ever-evolving landscape of web development, JavaScript remains a cornerstone technology powering countless large-scale web applications. While...
    编程 发布于2024-11-07
  • 为什么通过非常量指针修改“const”变量看起来有效,但实际上并没有改变它的值?
    为什么通过非常量指针修改“const”变量看起来有效,但实际上并没有改变它的值?
    通过非常量指针修改 const在 C 中,const 变量一旦初始化就无法修改。但是,在某些情况下,const 变量可能会被更改。考虑以下代码:const int e = 2; int* w = (int*)&e; // (1) *w = 5; ...
    编程 发布于2024-11-07
  • Android - 将 .aab 文件上传到 Play 商店时出错
    Android - 将 .aab 文件上传到 Play 商店时出错
    如果您遇到此错误,请按照以下步骤操作以确保与您的包名称和签名密钥保持一致: 确保 app.json 文件中的包名称与您第一次上传 .aab 文件时使用的包名称匹配。 "android": { "permissions":["CAMERA","READ_EXTERNAL_STORAGE...
    编程 发布于2024-11-07
  • 如何使用 PHP 将 HTML 转换为 PDF
    如何使用 PHP 将 HTML 转换为 PDF
    (适用于 Windows 的指南。不适用于 Mac 或 Linux) (图片来源) 在 PHP 中将 HTML 转换为 PDF 的方法不止一种。您可以使用Dompdf或Mpdf;但是,这两个库的执行方式有所不同。 注意:本文中并未包含所有解决方案。 要使用这两个库,您将需要 Composer。 ...
    编程 发布于2024-11-07
  • C++ 会拥抱垃圾收集吗?
    C++ 会拥抱垃圾收集吗?
    C 中的垃圾收集:实现和共识的问题虽然有人建议 C 最终会包含垃圾收集器,但它仍然是争论和持续发展的主题。要理解其中的原因,我们必须深入研究迄今为止阻碍其纳入的挑战和考虑因素。实现复杂性向 C 添加隐式垃圾收集是一个非-琐碎的任务。该语言的低级性质和对指针的广泛支持带来了重大的技术障碍。实施问题的范...
    编程 发布于2024-11-07

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3