Quand JavaScript est-il synchrone?


202

J'ai eu l'impression que JavaScript était toujours asynchrone. Cependant, j'ai appris qu'il y a des situations où ce n'est pas le cas (c'est-à-dire les manipulations DOM). Y a-t-il une bonne référence quelque part sur le moment où il sera synchrone et quand il sera asynchrone? JQuery affecte-t-il cela du tout?


14
Toujours à l'exception de ajax.
defau1t

La réponse acceptée est fausse et trompeuse, veuillez la vérifier.
Suraj Jain

2
Était également utile de regarder youtube.com/watch?v=8aGhZQkoFbQ pour comprendre la boucle d'événements et le fonctionnement de la pile, des API Web et de la file d'attente des tâches en ce qui concerne la synchronisation et l'asynchronisation
mtpultz

1
@ defau1t N'est-ce pas faux, JavaScript est toujours synchrone, lorsque l'appel ajax termine le rappel se retrouve dans la file d'attente, comment est-ce une exception à la nature synchrone du script java.
Suraj Jain

Réponses:


281

JavaScript est toujours synchrone et monothread. Si vous exécutez un bloc de code JavaScript sur une page, aucun autre JavaScript de cette page ne sera actuellement exécuté.

JavaScript est uniquement asynchrone dans le sens où il peut effectuer, par exemple, des appels Ajax. L'appel Ajax cessera de s'exécuter et tout autre code pourra s'exécuter jusqu'à ce que l'appel revienne (avec succès ou non), moment auquel le rappel s'exécutera de manière synchrone. Aucun autre code ne sera exécuté à ce stade. Il n'interrompra aucun autre code en cours d'exécution.

Les minuteries JavaScript fonctionnent avec ce même type de rappel.

Décrire JavaScript comme asynchrone est peut-être trompeur. Il est plus précis de dire que JavaScript est synchrone et monothread avec divers mécanismes de rappel.

jQuery a une option sur les appels Ajax pour les faire de manière synchrone (avec l' async: falseoption). Les débutants pourraient être tentés de l'utiliser incorrectement car cela permet un modèle de programmation plus traditionnel auquel on pourrait être plus habitué. La raison pour laquelle cela pose problème est que cette option bloquera tout le JavaScript sur la page jusqu'à ce qu'elle se termine, y compris tous les gestionnaires d'événements et les minuteurs.


31
désolé, je n'ai pas bien compris cette déclaration "Le code cessera de s'exécuter jusqu'à ce que l'appel revienne (avec succès ou par erreur)". pourriez-vous élaborer. Comment cette affirmation peut-elle être vraie lorsque vous dites également "Cela n'interrompra aucun autre code en cours d'exécution"; Parlez-vous du code de rappel uniquement dans la première instruction? Veuillez m'éclairer.
krishna

2
Nettuts a un tutoriel qui explique assez bien les bases de l'async ici: net.tutsplus.com/tutorials/javascript-ajax/…
RobW

26
@cletus L'instruction "Le code cessera de s'exécuter jusqu'à ce que l'appel revienne" doit être corrigée car l'exécution ne s'arrête pas. L'exécution du code peut continuer. Sinon, cela signifierait que l'appel est synchrone.
HS.

1
Je n'ai pas compris cette déclaration aussi.
Towry

12
Cette réponse est incroyablement trompeuse et déroutante. Veuillez plutôt consulter la réponse de CMS ou de Faraz Ahmad.
iono

214

JavaScript est monothread et possède un modèle d'exécution synchrone. Un thread unique signifie qu'une seule commande est exécutée à la fois. Synchrone signifie une à la fois, c'est-à-dire qu'une ligne de code est exécutée à la fois afin que le code apparaisse. Donc, en JavaScript, une chose se produit à la fois.

Contexte d'exécution

Le moteur JavaScript interagit avec les autres moteurs du navigateur. Dans la pile d'exécution JavaScript, il y a un contexte global en bas, puis lorsque nous invoquons des fonctions, le moteur JavaScript crée de nouveaux contextes d'exécution pour les fonctions respectives. Lorsque la fonction appelée quitte son contexte d'exécution est extrait de la pile, puis le contexte d'exécution suivant est extrait et ainsi de suite ...

Par exemple

function abc()
{
   console.log('abc');
}


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();

Dans le code ci-dessus, un contexte d'exécution global sera créé et dans ce contexte var one sera stocké et sa valeur sera 1 ... lorsque l'invocation xyz () est appelée, un nouveau contexte d'exécution sera créé et si nous avions défini une variable dans la fonction xyz, ces variables seraient stockées dans le contexte d'exécution de xyz (). Dans la fonction xyz, nous invoquons abc () puis le contexte d'exécution abc () est créé et placé sur la pile d'exécution ... Maintenant, lorsque abc () termine, son contexte est extrait de la pile, puis le contexte xyz () est extrait de pile et puis le contexte global sera sauté ...

Maintenant sur les rappels asynchrones; asynchrone signifie plus d'un à la fois.

Tout comme la pile d'exécution, il y a la file d'attente d'événements . Lorsque nous voulons être informés d'un événement dans le moteur JavaScript, nous pouvons l'écouter et cet événement est placé dans la file d'attente. Par exemple, un événement de demande Ajax ou un événement de demande HTTP.

Chaque fois que la pile d'exécution est vide, comme illustré dans l'exemple de code ci-dessus, le moteur JavaScript examine périodiquement la file d'attente des événements et voit s'il y a un événement à notifier. Par exemple, dans la file d'attente, il y avait deux événements, une demande ajax et une demande HTTP. Il cherche également à voir s'il existe une fonction qui doit être exécutée sur ce déclencheur d'événement ... Le moteur JavaScript est donc informé de l'événement et connaît la fonction respective à exécuter sur cet événement ... Le moteur JavaScript appelle donc le fonction de gestionnaire, dans le cas d'exemple, par exemple AjaxHandler () sera invoqué et comme toujours quand une fonction est invoquée son contexte d'exécution est placé sur le contexte d'exécution et maintenant l'exécution de la fonction se termine et la requête ajax d'événement est également supprimée de la file d'attente d'événements ... Une fois AjaxHandler () terminé, la pile d'exécution est vide, de sorte que le moteur examine à nouveau la file d'attente d'événements et exécute la fonction de gestionnaire d'événements de la requête HTTP qui se trouvait ensuite dans la file d'attente. Il est important de se rappeler que la file d'attente d'événements n'est traitée que lorsque la pile d'exécution est vide.

Par exemple, voir le code ci-dessous expliquant la pile d'exécution et la gestion de la file d'attente d'événements par le moteur Javascript.

function waitfunction() {
    var a = 5000 + new Date().getTime();
    while (new Date() < a){}
    console.log('waitfunction() context will be popped after this line');
}

function clickHandler() {
    console.log('click event handler...');   
}

document.addEventListener('click', clickHandler);


waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

Et

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>

Maintenant, lancez la page Web et cliquez sur la page, et voyez la sortie sur la console. La sortie sera

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

Le moteur JavaScript exécute le code de manière synchrone comme expliqué dans la partie contexte d'exécution, le navigateur place les choses de manière asynchrone dans la file d'attente des événements. Ainsi, les fonctions qui prennent très longtemps à s'achever peuvent interrompre la gestion des événements. Les choses qui se passent dans un navigateur comme les événements sont gérées de cette façon par JavaScript, s'il y a un écouteur censé s'exécuter, le moteur l'exécutera lorsque la pile d'exécution sera vide. Et les événements sont traités dans l'ordre où ils se produisent, donc la partie asynchrone concerne ce qui se passe en dehors du moteur, c'est-à-dire ce que le moteur doit faire lorsque ces événements extérieurs se produisent.

JavaScript est donc toujours synchrone.


16
Cette réponse est très claire, elle devrait recevoir plus de votes positifs.
ranu

7
Certainement la meilleure explication du comportement asynchrone de Javascript que j'ai lue.
Charles Jaimet

1
Belle explication du contexte d'exécution et de la file d'attente.
Divyanshu Maithani

1
Bien sûr, cela nécessite que vous lisiez un peu la pile de contexte d'exécution, et seulement l'ajout de celle-ci étant vide et que l'événement me donne enfin l'impression de comprendre déterministe l'excution du script java. Ce qui est pire, c'est que je pense que cela ne prend qu'une page de lecture, mais je ne le trouve presque nulle part. Alors pourquoi personne ne le dit? Soit ils ne savent pas ou quoi? Mais je pense que si un tutoriel js avait cela, il aurait pu me faire gagner beaucoup de temps. >: |
métier de maréchal le

2
Explication parfaite!
juillet 2017 à 10h03

100

JavaScript est monothread et vous travaillez tout le temps sur une exécution normale de flux de code synchrone.

De bons exemples du comportement asynchrone que JavaScript peut avoir sont les événements (interaction de l'utilisateur, résultats des requêtes Ajax, etc.) et les temporisateurs, essentiellement des actions qui peuvent se produire à tout moment.

Je vous recommande de jeter un œil à l'article suivant:

Cet article vous aidera à comprendre la nature monothread de JavaScript et comment les minuteurs fonctionnent en interne et comment fonctionne l'exécution asynchrone de JavaScript.

async


La réponse acceptée induit en erreur peut-on faire quelque chose dans ce cas? /
Suraj Jain

8

Pour quelqu'un qui comprend vraiment comment fonctionne JS, cette question peut sembler fausse, mais la plupart des gens qui utilisent JS n'ont pas un niveau de compréhension aussi profond (et n'en ont pas nécessairement besoin) et pour eux, c'est un point assez déroutant, je le ferai essayez de répondre de ce point de vue.

JS est synchrone dans la façon dont son code est exécuté. chaque ligne ne s'exécute qu'après la ligne avant qu'elle ne soit terminée et si cette ligne appelle une fonction après cela, ect ...

Le principal point de confusion provient du fait que votre navigateur est en mesure de dire à JS d'exécuter plus de code à tout moment (simmlar à la façon dont vous pouvez excuter plus de code JS sur une page de la console). Par exemple, JS a des fonctions de rappel qui ont pour but de permettre à JS de se comporter de manière asynchrone afin que d'autres parties de JS puissent s'exécuter en attendant qu'une fonction JS qui a été exécutée (IE un GETappel) renvoie une réponse, JS continuera à s'exécuter jusqu'à le navigateur a une réponse à ce stade, la boucle d'événements (navigateur) exécutera le code JS qui appelle la fonction de rappel.

Étant donné que la boucle d'événements (navigateur) peut entrer plus de JS à exécuter à tout moment dans ce sens, JS est asynchrone (les principaux éléments qui amèneront un navigateur à entrer du code JS sont les délais d'attente, les rappels et les événements)

J'espère que cela est suffisamment clair pour être utile à quelqu'un.


4

Définition

Le terme «asynchrone» peut être utilisé dans des sens légèrement différents, ce qui donne lieu à des réponses apparemment contradictoires, alors qu'elles ne le sont pas réellement. Wikipédia sur Asynchrony a cette définition:

L'asynchronie, dans la programmation informatique, se réfère à l'occurrence d'événements indépendants du flux principal du programme et des moyens de gérer ces événements. Il peut s'agir d'événements "extérieurs" tels que l'arrivée de signaux, ou d'actions déclenchées par un programme qui se déroulent en même temps que l'exécution du programme, sans que le programme se bloque pour attendre les résultats.

le code non JavaScript peut mettre en file d'attente de tels événements "extérieurs" à certaines des files d'attente d'événements de JavaScript. Mais c'est aussi loin que ça va.

Pas de préemption

Il n'y a pas externe interruption de l'exécution du code JavaScript afin d'exécuter un autre code JavaScript dans votre script. Des morceaux de JavaScript sont exécutés les uns après les autres, et l'ordre est déterminé par l'ordre des événements dans chaque file d'attente d'événements et la priorité de ces files d'attente.

Par exemple, vous pouvez être absolument sûr qu'aucun autre JavaScript (dans le même script) ne s'exécutera jamais pendant l'exécution du morceau de code suivant:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
    sum += a[i];
}

En d'autres termes, il n'y a pas de préemption en JavaScript. Quoi qu'il puisse y avoir dans les files d'attente d'événements, le traitement de ces événements devra attendre que ce morceau de code soit terminé. La spécification EcmaScript dit dans section 8.4 Travaux et files d'attente de travaux :

L'exécution d'un Job ne peut être lancée que lorsqu'il n'y a pas de contexte d'exécution en cours et que la pile de contextes d'exécution est vide.

Exemples d'asynchronie

Comme d'autres l'ont déjà écrit, il existe plusieurs situations où l'asynchronie entre en jeu dans JavaScript, et cela implique toujours une file d'attente d'événements, qui ne peut entraîner l'exécution de JavaScript que lorsqu'il n'y a pas d'autre code JavaScript en cours d'exécution:

  • setTimeout(): l'agent (par exemple le navigateur) mettra un événement dans une file d'attente d'événements une fois le délai expiré. La surveillance de l'heure et le placement de l'événement dans la file d'attente se fait par du code non JavaScript, et vous pouvez donc imaginer que cela se produit en parallèle avec l'exécution potentielle de certains codes JavaScript. Mais le rappel fourni à setTimeoutne peut s'exécuter que lorsque le code JavaScript en cours d'exécution s'est terminé et que la file d'attente d'événements appropriée est en cours de lecture.

  • fetch(): l'agent utilisera les fonctions du système d'exploitation pour effectuer une requête HTTP et surveiller toute réponse entrante. Encore une fois, cette tâche non JavaScript peut s'exécuter en parallèle avec du code JavaScript toujours en cours d'exécution. Mais la procédure de résolution de promesse, qui résoudra la promesse retournée par fetch(), ne peut être exécutée que lorsque le JavaScript en cours d'exécution est terminé.

  • requestAnimationFrame(): le moteur de rendu du navigateur (non JavaScript) placera un événement dans la file d'attente JavaScript lorsqu'il sera prêt à effectuer une opération de peinture. Lorsque l'événement JavaScript est traité, la fonction de rappel est exécutée.

  • queueMicrotask(): place immédiatement un événement dans la file d'attente des microtâches. Le rappel sera exécuté lorsque la pile d'appels est vide et que cet événement est consommé.

Il existe de nombreux autres exemples, mais toutes ces fonctions sont fournies par l'environnement hôte, et non par le noyau EcmaScript. Avec le noyau EcmaScript, vous pouvez synchroniser un événement dans une file d'attente de travaux Promise avecPromise.resolve() .

Constructions de langage

EcmaScript fournit plusieurs constructions de langage pour soutenir le modèle de asynchronisme, tels que yield, async, await. Mais ne vous y trompez pas: aucun code JavaScript ne sera interrompu par un événement externe. L '"interruption" qui yieldet awaitsemble fournir n'est qu'un moyen contrôlé et prédéfini de revenir d'un appel de fonction et de restaurer son contexte d'exécution ultérieurement, soit par le code JS (dans le cas de yield), soit par la file d'attente des événements (dans le cas deawait ).

Gestion des événements DOM

Lorsque le code JavaScript accède à l'API DOM, cela peut dans certains cas faire déclencher une ou plusieurs notifications synchrones par l'API DOM. Et si votre code a un gestionnaire d'événements qui l'écoute, il sera appelé.

Cela peut apparaître comme une concurrence préemptive, mais ce n'est pas le cas: une fois que votre ou vos gestionnaires d'événements retourneront, l'API DOM finira également par revenir et le code JavaScript d'origine continuera.

Dans d'autres cas, l'API DOM enverra simplement un événement dans la file d'attente d'événements appropriée, et JavaScript le récupérera une fois la pile d'appels vide.

Voir les événements synchrones et asynchrones


0

Est synchrone dans tous les cas.

Exemple de blocage de thread avec Promises:

  const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');

La sortie sera:

Test start...
Test finish...
Finish!
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.