Maison  >  Article  >  interface Web  >  Comprendre la programmation asynchrone en JavaScript : guide du débutant sur la boucle d'événements

Comprendre la programmation asynchrone en JavaScript : guide du débutant sur la boucle d'événements

PHPz
PHPzoriginal
2024-09-11 06:32:03679parcourir

Understanding Asynchronous Programming in JavaScript: Beginner

Vous êtes-vous déjà demandé pourquoi certains morceaux de code JavaScript semblent fonctionner dans le désordre ? La clé pour comprendre cela est la boucle d'événements.

La boucle d'événements JavaScript peut être difficile à comprendre, en particulier lorsqu'il s'agit de différents types d'opérations asynchrones. Dans cet article, nous expliquerons comment JavaScript gère le code synchrone et asynchrone, les microtâches et les macrotâches, et pourquoi certaines choses se produisent. dans un ordre précis.

Table des matières

  1. Codes synchrones et asynchrones
    • Qu'est-ce que le code synchrone
    • Qu'est-ce que le code asynchrone
    • Modèles asynchrones en JavaScript
    • Code synchrone ou asynchrone
  2. Microtâches et macrotâches
    • Que sont les microtâches
    • Que sont les macrotâches
    • Microtâches vs Macrotâches
  3. La boucle des événements
    • Qu'est-ce que la boucle d'événement
    • Comment fonctionne la boucle d'événements
  4. Exemples
    • Exemple 1 : Minuterie avec promesses et boucle d'événement
    • Exemple 2 : promesses et minuteries imbriquées
    • Exemple 3 : Opérations mixtes synchrones et asynchrones
  5. Conclusion

Codes synchrones et asynchrones

JavaScript gère les opérations de deux manières principales : synchrone et asynchrone. Comprendre la différence entre eux est essentiel pour comprendre comment JavaScript gère les tâches et comment écrire du code efficace et non bloquant.

Qu'est-ce que le code synchrone ?

Le code synchrone est la valeur par défaut en JavaScript, ce qui signifie que chaque ligne s'exécute l'une après l'autre dans l'ordre. Par exemple :

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

Cela affichera :

First
Second

Qu'est-ce que le code asynchrone ?

Le code asynchrone, quant à lui, permet à certaines tâches de s'exécuter en arrière-plan et de se terminer plus tard, sans bloquer le reste du code. Des fonctions comme setTimeout() ou Promise sont des exemples de code asynchrone.

Voici un exemple simple de code asynchrone utilisant setTimeout() :

console.log("First");

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

console.log("Third");

Cela affichera :

First
Third
Second

Modèles asynchrones en JavaScript :

Il existe plusieurs façons de gérer les opérations asynchrones en JavaScript :

  1. Callbacks : Une fonction passée en argument à une autre fonction, et exécutée une fois que la première fonction a terminé sa tâche.

Exemple de code :

console.log("Start");

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

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

console.log("End");
  1. Promesses : Une promesse représente une valeur future (ou une erreur) qui sera éventuellement renvoyée par la fonction asynchrone.

Exemple de code :

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 est un sucre syntaxique construit sur des promesses, nous permettant d'écrire du code asynchrone qui semble synchrone.

Exemple de code :

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

Code synchrone ou asynchrone

Pour mieux comprendre chacune de ces méthodes d'exécution de javascript et en quoi elles diffèrent de chacune, voici une description détaillée des différences entre plusieurs aspects des fonctions 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

La boucle des événements

La boucle d'événements est un concept fondamental en JavaScript qui permet des opérations asynchrones non bloquantes même si JavaScript est monothread. Il est chargé de gérer les rappels asynchrones et de garantir que JavaScript continue de fonctionner correctement sans être bloqué par des opérations fastidieuses.

Qu'est-ce que la boucle d'événements

La boucle d'événements est un mécanisme qui permet à JavaScript de gérer efficacement les opérations asynchrones. Il vérifie en permanence la pile d'appels et la file d'attente des tâches (ou file d'attente des microtâches) pour déterminer quelle fonction doit être exécutée ensuite.

Pour mieux comprendre la boucle des événements, il est important de savoir comment fonctionne JavaScript en interne. Il est important de noter que JavaScript est un langage à thread unique, ce qui signifie qu'il ne peut faire qu'une seule chose à la fois. Il n'y a qu'une seule pile d'appels, qui stocke les fonctions à exécuter. Cela rend le code synchrone simple, mais cela pose un problème pour des tâches telles que la récupération de données sur un serveur ou la définition d'un délai d'attente, qui prennent du temps. Sans la boucle d'événements, JavaScript serait bloqué en attendant ces tâches et rien d'autre ne se produirait.

Comment fonctionne la boucle d'événements

1. Pile d'appels :

La pile d'appels est l'endroit où la fonction en cours d'exécution est conservée. JavaScript ajoute et supprime des fonctions de la pile d'appels au fur et à mesure qu'il traite le code.

2. Démarrages de tâches asynchrones :

Lorsqu'une tâche asynchrone telle que setTimeout, fetch ou Promise est rencontrée, JavaScript délègue cette tâche aux API Web du navigateur (telles que l'API Timer, l'API réseau, etc.), qui gèrent la tâche en arrière-plan.

3. La tâche est déplacée vers la file d'attente des tâches :

Une fois la tâche asynchrone terminée (par exemple, la minuterie se termine ou les données sont reçues du serveur), le rappel (la fonction pour gérer le résultat) est déplacé vers la file d'attente des tâches (ou la file d'attente des microtâches dans le cas de promesses) .

4. La pile d'appels termine l'exécution en cours :

JavaScript continue d'exécuter le code synchrone. Une fois la pile d'appels vide, la boucle d'événements récupère la première tâche de la file d'attente des tâches (ou de la file d'attente des microtâches) et la place sur la pile d'appels pour exécution.

5. Répétez :

Ce processus se répète. La boucle d'événements garantit que toutes les tâches asynchrones sont traitées une fois les tâches synchrones en cours terminées.

Exemples

Maintenant que nous comprenons mieux et plus clairement le fonctionnement de la boucle événementielle, examinons quelques exemples pour consolider notre compréhension.

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn