Heim  >  Artikel  >  Web-Frontend  >  Asynchrone Programmierung in JavaScript verstehen: Einsteigerleitfaden zur Ereignisschleife

Asynchrone Programmierung in JavaScript verstehen: Einsteigerleitfaden zur Ereignisschleife

PHPz
PHPzOriginal
2024-09-11 06:32:03679Durchsuche

Understanding Asynchronous Programming in JavaScript: Beginner

Haben Sie sich jemals gefragt, warum einige Teile des JavaScript-Codes scheinbar nicht in der richtigen Reihenfolge sind? Der Schlüssel zum Verständnis ist die Ereignisschleife.

Die Ereignisschleife von JavaScript kann schwierig zu verstehen sein, insbesondere wenn es um verschiedene Arten asynchroner Vorgänge geht. In diesem Artikel erklären wir, wie JavaScript mit synchronem und asynchronem Code, Mikrotasks und Makrotasks umgeht und warum bestimmte Dinge passieren in einer bestimmten Reihenfolge.

Inhaltsverzeichnis

  1. Synchrone und asynchrone Codes
    • Was ist synchroner Code?
    • Was ist asynchroner Code?
    • Asynchrone Muster in JavaScript
    • Synchroner vs. asynchroner Code
  2. Mikrotasks und Makrotasks
    • Was sind Mikrotasks?
    • Was sind Makrotasks?
    • Mikrotasks vs. Makrotasks
  3. Die Ereignisschleife
    • Was ist die Ereignisschleife?
    • So funktioniert die Ereignisschleife
  4. Beispiele
    • Beispiel 1: Timer mit Versprechen und Ereignisschleife
    • Beispiel 2: Verschachtelte Versprechen und Timer
    • Beispiel 3: Gemischte synchrone und asynchrone Operationen
  5. Fazit

Synchrone und asynchrone Codes

JavaScript verarbeitet Vorgänge im Wesentlichen auf zwei Arten: synchron und asynchron. Das Verständnis der Unterschiede zwischen ihnen ist der Schlüssel zum Verständnis, wie JavaScript Aufgaben erledigt und wie man effizienten und nicht blockierenden Code schreibt.

Was ist synchroner Code?

Synchroner Code ist die Standardeinstellung in JavaScript, was bedeutet, dass jede Zeile nacheinander ausgeführt wird. Zum Beispiel:

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

Dies wird Folgendes ausgeben:

First
Second

Was ist asynchroner Code?

Asynchroner Code hingegen ermöglicht es, dass bestimmte Aufgaben im Hintergrund ausgeführt und später abgeschlossen werden, ohne den Rest des Codes zu blockieren. Funktionen wie setTimeout() oder Promise sind Beispiele für asynchronen Code.

Hier ist ein einfaches Beispiel für asynchronen Code mit setTimeout():

console.log("First");

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

console.log("Third");

Dies wird Folgendes ausgeben:

First
Third
Second

Asynchrone Muster in JavaScript:

Es gibt mehrere Möglichkeiten, asynchrone Vorgänge in JavaScript zu handhaben:

  1. Rückrufe: Eine Funktion, die als Argument an eine andere Funktion übergeben und ausgeführt wird, nachdem die erste Funktion ihre Aufgabe abgeschlossen hat.

Codebeispiel:

console.log("Start");

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

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

console.log("End");
  1. Versprechen: Ein Versprechen stellt einen zukünftigen Wert (oder Fehler) dar, der schließlich von der asynchronen Funktion zurückgegeben wird.

Codebeispiel:

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 ist syntaktischer Zucker, der auf Versprechen aufbaut und es uns ermöglicht, asynchronen Code zu schreiben, der synchron aussieht.

Codebeispiel:

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

Synchroner vs. asynchroner Code

Um jede dieser Methoden zur Ausführung von Javascript besser zu verstehen und zu verstehen, wie sie sich voneinander unterscheiden, finden Sie hier eine ausführliche Beschreibung der Unterschiede in mehreren Aspekten von Javascript-Funktionen.

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

Die Ereignisschleife

Die Ereignisschleife ist ein grundlegendes Konzept in JavaScript, das nicht blockierende asynchrone Vorgänge ermöglicht, obwohl JavaScript Single-Threaded ist. Es ist für die Verarbeitung asynchroner Rückrufe verantwortlich und stellt sicher, dass JavaScript weiterhin reibungslos läuft, ohne durch zeitaufwändige Vorgänge blockiert zu werden.

Was ist die Ereignisschleife?

Die Ereignisschleife ist ein Mechanismus, der es JavaScript ermöglicht, asynchrone Vorgänge effizient abzuwickeln. Es überprüft kontinuierlich den Aufrufstapel und die Task-Warteschlange (oder Mikrotask-Warteschlange), um zu bestimmen, welche Funktion als nächstes ausgeführt werden soll.

Um die Ereignisschleife besser zu verstehen, ist es wichtig zu wissen, wie JavaScript intern funktioniert. Es ist wichtig zu beachten, dass JavaScript eine Single-Threaded-Sprache ist, was bedeutet, dass sie jeweils nur eine Sache ausführen kann. Es gibt nur einen Aufrufstapel, der die auszuführenden Funktionen speichert. Dies macht synchronen Code unkompliziert, stellt jedoch ein Problem für Aufgaben wie das Abrufen von Daten von einem Server oder das Festlegen eines Timeouts dar, deren Ausführung einige Zeit in Anspruch nimmt. Ohne die Ereignisschleife würde JavaScript auf diese Aufgaben warten und nichts anderes würde passieren.

So funktioniert die Ereignisschleife

1. Aufrufstapel:

Im Aufrufstapel wird die aktuell ausgeführte Funktion gespeichert. JavaScript fügt während der Codeverarbeitung Funktionen zur Aufrufliste hinzu und entfernt sie daraus.

2. Asynchrone Task-Starts:

Wenn eine asynchrone Aufgabe wie setTimeout, fetch oder Promise auftritt, delegiert JavaScript diese Aufgabe an die Web-APIs des Browsers (wie Timer-API, Netzwerk-API usw.), die die Aufgabe im Hintergrund verarbeiten.

3. Aufgabe wird in die Aufgabenwarteschlange verschoben:

Sobald die asynchrone Aufgabe abgeschlossen ist (z. B. der Timer abgelaufen ist oder Daten vom Server empfangen wurden), wird der Rückruf (die Funktion zur Verarbeitung des Ergebnisses) in die Aufgabenwarteschlange (oder Mikrotask-Warteschlange im Fall von Versprechen) verschoben. .

4. Aufrufstapel beendet aktuelle Ausführung:

JavaScript führt weiterhin den synchronen Code aus. Sobald der Aufrufstapel leer ist, holt die Ereignisschleife die erste Aufgabe aus der Aufgabenwarteschlange (oder Mikrotaskwarteschlange) und platziert sie zur Ausführung auf dem Aufrufstapel.

5. Wiederholen:

Dieser Vorgang wiederholt sich. Die Ereignisschleife stellt sicher, dass alle asynchronen Aufgaben bearbeitet werden, nachdem die aktuellen synchronen Aufgaben erledigt sind.

Beispiele

Da wir nun besser und klarer verstehen, wie die Ereignisschleife funktioniert, schauen wir uns einige Beispiele an, um unser Verständnis zu festigen.

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 ?‍??

Das obige ist der detaillierte Inhalt vonAsynchrone Programmierung in JavaScript verstehen: Einsteigerleitfaden zur Ereignisschleife. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn