1103 votes

Pourquoi setTimeout (fn, 0) est-il parfois utile?

J'ai récemment un assez vilain bug, dans lequel le code a été chargement d'un <select> dynamiquement via JavaScript. Cette chargées dynamiquement <select> pré-valeur sélectionnée. Dans IE6, nous avons déjà eu le code pour corriger la <option>, parce que parfois l' <select>s' selectedIndex de la valeur serait hors de synchronisation avec le" <option>s' index d'attribut, comme ci-dessous:

field.selectedIndex = element.index;

Toutefois, ce code ne fonctionnait pas. Même si le domaine de l' selectedIndex a été définie correctement, le mauvais index est sélectionné. Cependant, si j'ai collé un alert() déclaration au bon moment, la bonne option serait sélectionné. Pensant que ce pourrait être une sorte de problème de synchronisation, j'ai essayé quelque chose au hasard que je l'avais vu dans le code avant:

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

Et cela a fonctionné!

J'ai une solution pour mon problème, mais je suis mal à l'aise que je ne sais pas exactement pourquoi cela résout mon problème. Quelqu'un aurait-il une explication officielle? Ce problème de navigateur suis-je en évitant par l'appel de ma fonction "plus tard" à l'aide de setTimeout()?

984voto

staticsan Points 14435

Cela fonctionne parce que vous êtes en train de faire coopérative multi-tâches.

Un navigateur a faire un certain nombre de choses assez bien tout à la fois, et l'un de ceux qui est d'exécuter du JavaScript. Mais une des choses que JavaScript est très souvent utilisé pour demander au navigateur de construire un élément d'affichage. C'est souvent supposé être fait de façon synchrone (surtout que JavaScript n'est pas exécutée en parallèle), mais il n'est pas sûr que ce soit le cas et JavaScript n'ont pas un mécanisme défini pour l'attente.

La solution est de "suspendre" l'exécution de JavaScript, pour laisser le rendu des threads rattraper. Et c'est l'effet qu' setTimeout() avec un délai d'attente de 0. C'est comme un thread/processus de rendement à C. Bien qu'il semble dire "exécuter immédiatement" elle ne donne en fait que le navigateur est une chance de finir en faisant quelques aucun-JavaScript, des choses qui ont été en attente pour terminer avant de s'occuper de ce nouveau morceau de JavaScript.

(En réalité, setTimeout() requeues la nouvelle Javascript à la fin de l'exécution de la file d'attente. Voir les commentaires pour les liens vers une explication plus longue.)

IE6 arrive juste à être plus sujettes à cette erreur, mais je l'ai vu se produire sur les anciennes versions de Mozilla et FireFox.

726voto

DVK Points 63282

Préface:

REMARQUE IMPORTANTE: même si c'est plus upvoted et accepté, a accepté de répondre par @staticsan en fait n'est PAS CORRECT! - voir David Mulder commentaire pour expliquer pourquoi.

Certains des autres réponses sont correctes butdon pas vraiment d'illustrer ce que le problème est résolu, j'ai donc créé cette réponse de présenter en détail l'illustration.

En tant que tel, je suis de poster une présentation détaillée de ce que le navigateur ne et comment l'utilisation de l' setTimeout() aide. Il semble assez long, mais est en fait très simple et direct, j'ai juste fait qu'il est très détaillé.

Mise à JOUR: j'ai fait un JSFiddle pour vivre, de démontrer l'explication ci-dessous: http://jsfiddle.net/C2YBE/31/ . Beaucoup de choses à @ThangChung pour aider à kickstart.

UPDATE2: Juste au cas où JSFiddle site web meurt, ou supprime le code, j'ai ajouté le code pour cette réponse à la fin.


DÉTAILS:

Imaginez une application web avec un "faire quelque chose" bouton et un résultat div.

L' onClick gestionnaire pour "faire quelque chose" bouton appelle une fonction "LongCalc()", qui n'a que 2 choses:

  1. Fait un très long temps de calcul (dire prend 3 min)

  2. Imprime les résultats de calcul dans le résultat div.

Maintenant, à vos utilisateurs de commencer à tester cela, cliquez sur "faire quelque chose", et la page reste là, à faire semblant de rien pendant 3 minutes, ils s'impatientent, cliquez sur le bouton de nouveau, attendre 1 min, rien ne se passe, cliquez sur le bouton nouveau...

Le problème, c'est évident - vous voulez un "État" de la DIV, qui montre ce qu'il se passe. Nous allons voir comment cela fonctionne.


Si vous ajoutez un "État" de la DIV (initialement vide), et de modifier l' onlcick gestionnaire (fonction LongCalc()) à faire 4 choses:

  1. Remplir le statut de "Calcul... peut prendre environ 3 minutes" en état de DIV

  2. Fait un très long temps de calcul (dire prend 3 min)

  3. Imprime les résultats de calcul dans le résultat div.

  4. Remplir le statut de "Calcul" en état de DIV

Et, vous heureux de donner de l'application pour les utilisateurs à re-test.

Ils reviennent pour vous à la recherche très en colère. Et d'expliquer que lorsqu'ils ont cliqué sur le bouton, le Statut DIV ne l'ai jamais mis à jour avec "Calcul..."!!!


Vous vous grattez vous la tête, demandez autour de vous sur StackOverflow (ou de lire des docs ou google), et de réaliser le problème:

Le navigateur place l'ensemble de ses "TODO" tâches (à la fois de l'INTERFACE utilisateur des tâches et des commandes JavaScript) résultant d'événements en une seule file d'attente. Et malheureusement, re-dessiner le "Statut" des DIV avec la nouvelle "le Calcul..." la valeur est séparée TODO qui va jusqu'au bout de la file d'attente!

Voici une répartition des événements au cours de l'utilisateur de votre test, le contenu de la file d'attente après chaque événement:

  • File d'attente: [Empty]
  • Événement: Cliquez sur le bouton. La file d'attente après l'événement: [Execute OnClick handler(lines 1-4)]
  • Événement: Exécution de la première ligne dans le gestionnaire Onclick (par exemple, changement de Statut DIV vaue). La file d'attente après l'événement: [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]. Veuillez noter que le DOM changements se produire instantanément, à re-dessiner le correspondant de l'élément DOM vous avez besoin d'un nouvel événement, déclenchée par le DOM changement, qui est allé à la fin de la file d'attente.
  • PROBLÈME!!!!!! PROBLÈME!!!!!! Détails expliqués ci-dessous.
  • Événement: Exécution de la deuxième ligne du gestionnaire (calulation). File d'attente après: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value].
  • Événement: Exécuter 3d ligne dans le gestionnaire (remplir résultat DIV). File d'attente après: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result].
  • Événement: Exécution de la ligne 4 dans gestionnaire (remplir le statut de DIV avec "TERMINÉ"). File d'attente: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value].
  • Événement: exécuter implicite return de onclick gestionnaire de sous. Nous prenons la "Exécuter OnClick gestionnaire" de la file d'attente et de lancer l'exécution d'élément suivant dans la file d'attente.
  • REMARQUE: comme nous l'avons déjà fini le calcul, à 3 minutes déjà passé pour l'utilisateur. Le re-dessiner événement n'arrive pas encore!!!
  • Événement: re-dessiner le Statut de DIV avec "Calcul de la" valeur. Nous ne le re-dessiner et prendre la file d'attente.
  • Événement: re-dessiner Résultat DIV avec la valeur du résultat. Nous ne le re-dessiner et prendre la file d'attente.
  • Événement: re-dessiner le Statut de DIV avec "Faire" de la valeur. Nous ne le re-dessiner et prendre la file d'attente. Sharp-eyed les téléspectateurs pourraient même avis "Statut" DIV " avec "Calcul de la" valeur clignotante pour la fraction de microseconde APRÈS LE CALCUL TERMINÉ

Donc, le problème sous-jacent est que la re-tirer de l'événement pour "Statut" DIV est placé dans la file d'attente à la fin, APRÈS la "exécuter la ligne 2" événement qui prend 3 minutes, donc la re-dessiner n'arrive pas jusqu'à ce que, APRÈS le calcul est fait.


Le sauvetage est l' setTimeout(). Comment peut-il aider? Car en appelant au long de l'exécution de code via setTimeout, vous avez réellement créer 2 événements: setTimeout exécution elle-même, et (en raison de 0 timeout), séparé de la file d'attente d'entrée pour le code en cours d'exécution.

Donc, pour résoudre votre problème, vous devez modifier votre onClick gestionnaire à DEUX états (dans une nouvelle fonction ou juste un bloc à l'intérieur d' onClick):

  1. Remplir le statut de "Calcul... peut prendre environ 3 minutes" en état de DIV

  2. Exécuter setTimeout() 0 délai d'attente et un appel à l' LongCalc() fonction.

    LongCalc() fonction est presque la même que la dernière fois, mais n'a évidemment pas de "Calcul..." DIV mise à jour en tant que première étape; et au lieu de cela démarre le calcul immédiatement.

Alors, quelle est la séquence des événements et de la file d'attente maintenant?

  • File d'attente: [Empty]
  • Événement: Cliquez sur le bouton. La file d'attente après l'événement: [ExecuteOnClickhandler(status update,setTimeout()call)]
  • Événement: Exécution de la première ligne dans le gestionnaire Onclick (par exemple, changement de Statut DIV valeur). La file d'attente après l'événement: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value].
  • Événement: Exécution de la deuxième ligne du gestionnaire (setTimeout appel). File d'attente après: [re-draw Status DIV with "Calculating" value]. La file d'attente a rien de nouveau dans pour 0 secondes de plus.
  • Événement: l'Alarme de la temporisation s'en va, 0 secondes plus tard. File d'attente après: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)].
  • Événement: re-dessiner le Statut de DIV avec "Calcul de la" valeur. File d'attente après: [execute LongCalc (lines 1-3)]. Veuillez noter que cette ré-attirer l'événement pourrait effectivement se produire AVANT que l'alarme se déclenche, qui fonctionne tout aussi bien.
  • ...

Hoorray! Le Statut DIV viens de la mise à jour de "de Calcul..." avant le calcul commencé!!!



Ci-dessous est un exemple de code à partir de la JSFiddle illustrant ces exemples: http://jsfiddle.net/C2YBE/31/ :

Code HTML:

<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

Le code JavaScript: (Exécuté sur onDomReady et peut exiger jQuery 1.9)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calclation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});

94voto

Andy Points 628

Jetez un oeil à l’article de John Resig sur Comment fonctionnent les minuteries JavaScript. Lorsque vous définissez un délai d’attente, il met en fait le code asynchrone jusqu'à ce que le moteur exécute la pile des appels actuelle.

29voto

Jose Basilio Points 29215

``vous achète quelque temps jusqu'à ce que les éléments DOM sont chargés, même si a la valeur 0.

Vérifier cela : setTimeout

26voto

arley Points 473

La plupart des navigateurs disposent d'un processus unique, appelée main thread d'INTERFACE utilisateur qui est responsable d'exécuter du JavaScript et de l'INTERFACE utilisateur des mises à jour par exemple, la peinture, redessiner ou de redistribution.

Chaque exécution de JavaScript et de l'INTERFACE utilisateur mise à jour de tâches sont ajoutées à l'événement du navigateur système de file d'attente, alors que ces tâches sont envoyés au navigateur principal Thread d'INTERFACE utilisateur à effectuer.

Lorsque l'INTERFACE utilisateur des mises à jour sont produites lorsque le thread d'INTERFACE utilisateur est occupé, les tâches sont poussés dans l'INTERFACE utilisateur de système de file d'attente.

setTimeout(fn, 0); ajouter une tâche à l'INTERFACE de système de file d'attente après 0 millisecondes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X