>  기사  >  웹 프론트엔드  >  JavaScript의 비동기 프로그래밍 이해: 이벤트 루프 초보자 가이드

JavaScript의 비동기 프로그래밍 이해: 이벤트 루프 초보자 가이드

PHPz
PHPz원래의
2024-09-11 06:32:03679검색

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

이벤트 루프

이벤트 루프는 JavaScript가 단일 스레드임에도 불구하고 비차단 비동기 작업을 가능하게 하는 JavaScript의 기본 개념입니다. 비동기 콜백을 처리하고 시간이 많이 걸리는 작업으로 인해 차단되지 않고 JavaScript가 계속해서 원활하게 실행되도록 하는 역할을 담당합니다.

이벤트 루프란 무엇입니까?

이벤트 루프는 JavaScript가 비동기 작업을 효율적으로 처리할 수 있게 해주는 메커니즘입니다. 호출 스택과 작업 큐(또는 마이크로태스크 큐)를 지속적으로 확인하여 다음에 실행해야 할 기능을 결정합니다.

이벤트 루프를 더 잘 이해하려면 JavaScript가 내부적으로 어떻게 작동하는지 아는 것이 중요합니다. JavaScript는 단일 스레드 언어이므로 한 번에 한 가지 작업만 수행할 수 있다는 점을 기억하는 것이 중요합니다. 실행할 함수를 저장하는 호출 스택은 하나만 있습니다. 이는 동기 코드를 간단하게 만들지만, 서버에서 데이터를 가져오거나 시간 제한을 설정하는 등 완료하는 데 시간이 걸리는 작업에 문제를 야기합니다. 이벤트 루프가 없으면 JavaScript는 이러한 작업을 기다리며 멈춰서 아무 일도 일어나지 않을 것입니다.

이벤트 루프의 작동 방식

1. 호출 스택:

콜스택은 현재 실행 중인 함수가 보관되는 곳입니다. JavaScript는 코드를 처리하면서 호출 스택에 기능을 추가하고 제거합니다.

2. 비동기 작업 시작:

setTimeout, fetch 또는 Promise와 같은 비동기 작업이 발생하면 JavaScript는 해당 작업을 백그라운드에서 작업을 처리하는 브라우저의 웹 API(예: Timer API, Network API 등)에 위임합니다.

3. 작업이 작업 대기열로 이동합니다.

비동기 작업이 완료되면(예: 타이머가 완료되거나 서버에서 데이터가 수신됨) 콜백(결과를 처리하는 함수)이 작업 큐(또는 약속의 경우 마이크로태스크 큐)로 이동됩니다. .

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

Stay tuned and happy coding ?‍??

위 내용은 JavaScript의 비동기 프로그래밍 이해: 이벤트 루프 초보자 가이드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.