Pourquoi setTimeout(fn, 0) est-il parfois utile?
J'ai récemment rencontré un bug plutôt méchant, dans lequel le code chargeait un <select>
dynamiquement via JavaScript. Ce <select>
chargé dynamiquement avait une valeur présélectionnée. Dans IE6, nous avions déjà du code pour corriger le <option>
sélectionné, car parfois la valeur selectedIndex
de <select>
serait désynchronisée avec l'attribut index
de <option>
sélectionné, comme ci-dessous:
field.selectedIndex = element.index;
Cependant, ce code ne fonctionnait pas. Même si le champ selectedIndex
était correctement défini, le mauvais index finirait par être sélectionné. Cependant, si je collais une instruction alert()
au bon moment, l'option correcte serait sélectionnée. Pensant que cela pourrait être une sorte de problème de synchronisation, j'ai essayé quelque chose de aléatoire que j'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 à mon problème, mais je suis mal à l'aise de ne pas savoir exactement pourquoi cela résout mon problème. Quelqu'un aurait-il une explication officielle? Quel problème de navigateur est-ce que j'évite en appelant ma fonction" plus tard " en utilisant setTimeout()
?
17 réponses
Cela fonctionne parce que vous faites du multitâche coopératif.
Un navigateur doit faire un certain nombre de choses à peu près tout à la fois, et juste l'un d'entre eux est d'exécuter JavaScript. Mais une des choses que JavaScript est très souvent utilisé pour demander au navigateur de construire un élément d'affichage. Ceci est souvent supposé être fait de manière synchrone (d'autant plus que JavaScript N'est pas exécuté en parallèle) mais il n'y a aucune garantie que c'est le cas et JavaScript n'a pas de mécanisme bien défini pour attente.
La solution consiste à" mettre en pause " L'exécution JavaScript pour laisser les threads de rendu rattraper. Et c'est l'effet que setTimeout()
, avec un délai d'attente de 0 n'. C'est comme un rendement de thread/process en C. Bien qu'il semble dire "exécutez ceci immédiatement", il donne au navigateur une chance de finir de faire des choses non-JavaScript qui attendaient de finir avant de s'occuper de ce nouveau morceau de JavaScript.
(en réalité, setTimeout()
re-met en file d'attente le nouveau JavaScript à la fin de la file d'attente d'exécution. Voir les commentaires pour des liens vers une explication plus longue.)
IE6 se trouve être plus enclin à cette erreur, mais je l'ai vu se produire sur les anciennes versions de Mozilla et dans Firefox.
Voir Philip Roberts talk " Qu'est-ce que la boucle d'événement?" pour une explication plus approfondie.
Préface:
NOTE importante: bien qu'il soit le plus upvoted et accepté, la réponse acceptée par @staticsan est en fait pas correcte! - voir le commentaire de David Mulder pour expliquer pourquoi.
Certaines des autres réponses sont correctes mais n'illustrent pas réellement le problème résolu, alors j'ai créé cette réponse pour présenter cette illustration détaillée.
En tant que tel, je poste un détaillé de ce que fait le navigateur et comment utiliser setTimeout()
AIDE. Il semble long mais est en fait très simple et direct-je viens de le rendre très détaillé.
Mise à jour: j'ai fait un jsfiddle pour vivre-démontrer l'explication ci-dessous: http://jsfiddle.net/C2YBE/31/ . Beaucoup Merci à @ ThangChung pour avoir aidé à le démarrer.
UPDATE2: juste au cas où le site Web JSFiddle meurt, ou supprime le code, j'ai ajouté le code à cette réponse à la très fin.
DÉTAILS:
Imaginez une application web avec un bouton "Faire quelque chose" et un résultat div.
Le gestionnaire onClick
pour le bouton "Faire quelque chose" appelle une fonction "LongCalc ()", qui fait 2 choses:
-
Fait un calcul très long (disons prend 3 min)
Imprime les résultats du calcul dans le résultat div.
Maintenant, vos utilisateurs commencent à tester cela, cliquez sur le bouton" Faire quelque chose", et la page se trouve il ne fait apparemment rien pendant 3 minutes, ils s'agitent, cliquez à nouveau sur le bouton, attendez 1 min, rien ne se passe, cliquez à nouveau sur le bouton...
Le problème est évident - vous voulez un DIV" Status", qui montre ce qui se passe. Nous allons voir comment cela fonctionne.
Vous ajoutez donc un DIV" Status " (initialement vide), et modifiez le gestionnaire onclick
(function LongCalc()
) pour faire 4 choses:
-
Remplir le statut "calcul... peut prendre ~3 minutes " dans le statut DIV
-
Fait un calcul très long (disons prend 3 min)
Imprime les résultats du calcul dans le résultat div.
-
Remplissez le statut "calcul effectué" dans status DIV
Et, vous donnez heureusement l'application aux utilisateurs de re-tester.
Ils reviennent vers toi avec l'air très en colère. Et expliquez que quand ils ont cliqué sur le bouton, le statut DIV n'a jamais été mis à jour avec "calcul..." le statut!!!
Vous vous grattez la tête, demandez sur StackOverflow (ou lisez docs ou google), et réalisez le problème:
Le navigateur place toutes ses tâches" TODO " (tâches D'interface utilisateur et commandes JavaScript) résultant d'événements dans une file d'attente unique . Et malheureusement, re-dessiner le" statut "DIV avec le nouveau" calcul..."value est un TODO séparé qui va à la fin de la file d'attente!
Voici une ventilation des événements lors du test de votre utilisateur, contenu de la file d'attente après chaque événement:
- File d'attente:
[Empty]
- événement: cliquez sur le bouton. File d'attente après l'événement:
[Execute OnClick handler(lines 1-4)]
- événement: exécute la première ligne dans le gestionnaire OnClick (par exemple, change Status DIV value). 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 pendant que les changements DOM se produisent instantanément, pour redessiner l'élément DOM correspondant, vous avez besoin d'un nouvel événement, déclenché par le changement DOM, qui est allé à la fin de la file d'attente. - problème!!! problème!!! Détails expliqués ci-dessous.
- événement: exécute la deuxième ligne dans le gestionnaire (calcul). File d'attente après:
[Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]
. - événement: exécuter la 3ème ligne dans le gestionnaire (remplir le 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écutez la 4ème ligne dans le gestionnaire (remplissez le statut 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écute
return
implicite à partir du sous-Gestionnaireonclick
. Nous prenons le "Execute OnClick handler" hors de la file d'attente et commençons à exécuter l'élément suivant sur le file. - NOTE: puisque nous avons déjà terminé le calcul, 3 minutes se sont déjà écoulées pour l'utilisateur. l'événement de re-Tirage n'a pas encore eu lieu!!!
- Event: re-draw Status DIV avec la valeur "calcul". Nous faisons le re-tirage et retirons cela de la file d'attente.
- Événement: re-dessiner Résultat DIV avec la valeur du résultat. Nous faisons le re-tirage et retirons cela de la file d'attente.
- Event: re-draw Status DIV avec la valeur" Done". Nous faisons le re-tirage et retirons cela de la file d'attente. Téléspectateurs aux yeux pointus peut même remarquer "status DIV avec" calcul " valeur clignotant pour une fraction de microseconde - après le calcul terminé
Donc, le problème sous-jacent est que l'événement re-draw pour "Status" DIV est placé sur la file d'attente à la fin, après l'événement "execute line 2" qui prend 3 minutes, de sorte que le re-draw réel ne se produit qu'après le calcul.
À la rescousse vient le setTimeout()
. Comment peut-il aider? Parce qu'en appelant à long terme code via setTimeout
, vous créez en fait 2 événements: setTimeout
exécution elle-même, et (en raison de 0 délai d'attente), entrée de file d'attente séparée pour le code en cours d'exécution.
Donc, pour résoudre votre problème, vous modifiez votre gestionnaire onClick
pour qu'il soit deux instructions (dans une nouvelle fonction ou juste un bloc dans onClick
):
-
Remplir le statut "calcul... peut prendre ~3 minutes " dans le statut DIV
-
Exécuter
setTimeout()
avec 0 délai d'attente et un appel àLongCalc()
function.LongCalc()
la fonction est presque la même que la dernière fois mais n'a évidemment pas de "calcul"..."état DIV mise à jour comme première étape; et commence à la place le calcul tout de suite.
Alors, à quoi ressemble la séquence d'événements et la file d'attente maintenant?
- File d'attente:
[Empty]
- événement: cliquez sur le bouton. File d'attente après l'événement:
[Execute OnClick handler(status update, setTimeout() call)]
- événement: exécute la première ligne dans le gestionnaire OnClick (par exemple, change Status DIV value). 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écute la deuxième ligne dans le gestionnaire (appel setTimeout). File d'attente après:
[re-draw Status DIV with "Calculating" value]
. La file d'attente n'a rien de nouveau pendant 0 secondes de plus. - événement: L'alarme du délai d'attente s'éteint, 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 DIV avec la valeur "calcul" . File d'attente après:
[execute LongCalc (lines 1-3)]
. Veuillez noter que cet événement de re-draw peut réellement se produire avant que l'alarme ne se déclenche, ce qui fonctionne tout aussi bien. - ...
, Hourra! statut DIV vient d'être mis à jour à "calcul..."avant le calcul commencé!!!
Voici l'exemple de code du 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>
Code JavaScript: (exécuté sur onDomReady
et peut nécessiter 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('calculation 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);
});
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 file d'attente le code asynchrone jusqu'à ce que le moteur exécute la pile d'appels en cours.
setTimeout()
vous permet de gagner du temps jusqu'à ce que les éléments DOM soient chargés, même s'il est défini sur 0.
Vérifiez ceci: setTimeout
La plupart des navigateurs ont un processus appelé main thread, qui est responsable de l'exécution de certaines tâches JavaScript, des mises à jour de L'interface utilisateur, par exemple: peinture, redessiner ou refusion, etc.
Certaines tâches D'exécution JavaScript et de mise à jour de L'interface utilisateur sont mises en file d'attente dans la file d'attente des messages du navigateur, puis sont envoyées au thread principal du navigateur à exécuter.
Lorsque des mises à jour de L'interface utilisateur sont générées alors que le thread principal est occupé, les tâches sont ajoutées dans la file d'attente des messages.
setTimeout(fn, 0);
ajouter cette fn
à la fin de la file d'attente pour être exécuté.
Il planifie une tâche à ajouter dans la file d'attente des messages après un laps de temps donné.
Il y a des réponses contradictoires upvoted ici, et sans preuve, il n'y a aucun moyen de savoir qui croire. Voici la preuve que @ DVK est correct et @SalvadorDali est faux. Ce dernier affirme:
"Et voici pourquoi: il n'est pas possible d'avoir setTimeout avec un temps délai de 0 millisecondes. La valeur minimale est déterminée par navigateur et ce n'est pas 0 millisecondes. Historiquement les navigateurs définissent ceci minimum à 10 millisecondes, mais les spécifications HTML5 et les navigateurs modernes l'avoir fixé à 4 millisecondes."
Le délai d'attente minimum de 4 ms n'est pas pertinent pour ce qui se passe. Ce qui se passe vraiment, c'est que setTimeout pousse la fonction de rappel à la fin de la file d'attente d'exécution. Si après setTimeout (callback, 0) Vous avez un code de blocage qui prend plusieurs secondes à exécuter, le rappel ne sera pas exécuté pendant plusieurs secondes, jusqu'à ce que le code de blocage soit terminé. Essayez ce code:
function testSettimeout0 () {
var startTime = new Date().getTime()
console.log('setting timeout 0 callback at ' +sinceStart())
setTimeout(function(){
console.log('in timeout callback at ' +sinceStart())
}, 0)
console.log('starting blocking loop at ' +sinceStart())
while (sinceStart() < 3000) {
continue
}
console.log('blocking loop ended at ' +sinceStart())
return // functions below
function sinceStart () {
return new Date().getTime() - startTime
} // sinceStart
} // testSettimeout0
La sortie est:
setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033
Une raison de le faire est de reporter l'exécution du code à une boucle d'événement distincte. Lorsque vous répondez à un événement de navigateur (clic de souris, par exemple), il est parfois nécessaire d'effectuer des opérations uniquement après l'événement en cours est traité. L'installation setTimeout()
est le moyen le plus simple de le faire.
edit Maintenant que nous sommes en 2015, je devrais noter qu'il y a aussi requestAnimationFrame()
, ce qui n'est pas exactement la même chose mais c'est suffisamment proche de setTimeout(fn, 0)
que ça vaut le coup mentionner.
C'est une vieille question avec de vieilles réponses. Je voulais ajouter un nouveau regard sur ce problème et répondre pourquoi cela se produit et non pourquoi est-ce utile.
Vous avez Donc deux fonctions:
var f1 = function () {
setTimeout(function(){
console.log("f1", "First function call...");
}, 0);
};
var f2 = function () {
console.log("f2", "Second call...");
};
Puis appelez-les dans l'ordre suivant f1(); f2();
juste pour voir que le second s'exécute en premier.
Et voici pourquoi: il n'est pas possible d'avoir setTimeout
avec un délai de 0 milliseconde. La valeur minimale est déterminée par le navigateur et n'est pas de 0 milliseconde. historiquement les navigateurs définissent ce minimum à 10 millisecondes, mais les spécifications HTML5 et les navigateurs modernes l'ont défini à 4 millisecondes.
Si le niveau d'imbrication est supérieur à 5 et que le délai d'attente est inférieur à 4, alors augmentez le délai d'attente à 4.
Aussi de mozilla:
Pour implémenter un délai d'attente de 0 ms dans un navigateur moderne, vous pouvez utiliser fenêtre.postMessage() comme décrit ici.
P.S. l'information est prise après lire l'article suivant .
Comme il est passé une durée de 0
, Je suppose que c'est pour supprimer le code passé au setTimeout
du flux d'exécution. Donc, si c'est une fonction qui pourrait prendre un certain temps, cela n'empêchera pas le code suivant de s'exécuter.
L'autre chose que cela fait est de pousser l'invocation de la fonction au bas de la pile, empêchant un débordement de pile si vous appelez récursivement une fonction. Cela a l'effet d'une boucle while
mais permet au moteur JavaScript de déclencher d'Autres Minuteries asynchrones.
Les réponses sur les boucles d'exécution et le rendu du DOM avant la fin d'un autre code sont correctes. Zéro second délai D'attente en JavaScript aide à rendre le code pseudo-multithread, même si ce n'est pas le cas.
Je veux ajouter que la meilleure valeur pour un délai d'attente zéro seconde entre navigateurs / plateformes en JavaScript est en fait d'environ 20 millisecondes au lieu de 0 (zéro), car de nombreux navigateurs mobiles ne peuvent pas enregistrer des délais d'attente inférieurs à 20 millisecondes en raison des limitations d'horloge sur AMD puce.
En outre, les processus de longue durée qui n'impliquent pas de manipulation DOM doivent être envoyés aux travailleurs Web maintenant, car ils fournissent une véritable exécution multithread de JavaScript.
En appelant setTimeout, vous donnez à la page le temps de réagir à tout ce que fait l'utilisateur. Ceci est particulièrement utile pour les fonctions exécutées pendant le chargement de la page.
Quelques autres cas où setTimeout est utile:
Vous voulez briser une boucle ou un calcul de longue durée en composants plus petits afin que le navigateur ne semble pas "geler" ou dire "le Script sur la page est occupé".
Vous souhaitez désactiver un bouton de soumission de formulaire lorsque vous cliquez dessus, mais si vous désactivez le bouton dans le gestionnaire onClick, le formulaire ne sera pas soumis. setTimeout avec un temps de zéro fait l'affaire, permettant à l'événement de se terminer, le formulaire pour commencer à soumettre, alors votre bouton peut être désactivée.
SetTimout sur 0 est également très utile dans le modèle de mise en place d'une promesse différée, que vous voulez retourner tout de suite:
myObject.prototype.myMethodDeferred = function() {
var deferredObject = $.Deferred();
var that = this; // Because setTimeout won't work right with this
setTimeout(function() {
return myMethodActualWork.call(that, deferredObject);
}, 0);
return deferredObject.promise();
}
Ces deux réponses les mieux notées sont fausses. consultez la description MDN sur le modèle de concurrence et la boucle d'événement , et il devrait devenir clair ce qui se passe (cette ressource MDN est un vrai bijou). Et simplement en utilisant setTimeout
peut-être ajouter des problèmes inattendus dans votre code en plus de" résoudre " ce petit problème.
Qu'est-ce que en fait se passe ici n'est pas que "le navigateur pourrait ne pas être encore tout à fait prêt parce que la concurrence", ou quelque chose basé sur "chaque ligne est un événement qui est ajouté à l'arrière de la file d'attente".
Le jsfiddle fourni par DVK illustre en effet un problème, mais son explication n'est pas correcte.
Ce qui se passe dans son code, c'est qu'il attache d'abord un gestionnaire d'événements à l'événement click
sur le bouton #do
.
Ensuite, lorsque vous cliquez réellement sur le bouton, un {[3] } est créé référençant la fonction de gestionnaire d'événements, qui est ajoutée au message queue
. Lorsque le event loop
atteint ce message, il crée un frame
sur la pile, avec l'appel de fonction au gestionnaire d'événements click dans le jsfiddle.
Et c'est là que ça devient intéressant. Nous sommes tellement habitués à penser que Javascript est asynchrone que nous sommes enclins à négliger ce petit fait: toute image doit être exécutée, dans son intégralité, avant que la prochaine image puisse être exécutée. Pas de simultanéité, les gens.
Qu'est-ce que cela signifie? Cela signifie que chaque fois qu'une fonction est appelée à partir de la file d'attente de messages, elle bloque la file d'attente jusqu'à ce que la pile qu'elle génère ait été vidée. Ou, en termes plus généraux, il bloque jusqu'à ce que la fonction soit retournée. Et il bloque Tout , y compris les opérations de rendu DOM, le défilement et autres joyeusetés. Si vous voulez une confirmation, essayez simplement d'augmenter la durée de l'opération de longue durée dans le violon (par exemple, exécutez la boucle externe 10 fois de plus), et vous remarquerez que pendant son exécution, vous ne pouvez pas faire défiler la page. Si il fonctionne assez longtemps, votre navigateur vous demandera si vous voulez pour tuer le processus, car cela rend la page insensible. Le cadre est en cours d'exécution et la boucle d'événement et la file d'attente de messages sont bloquées jusqu'à la fin.
Alors pourquoi cet effet secondaire du texte ne pas mettre à jour? Parce que pendant que vous avez changé la valeur de l'élément dans le DOM - vous pouvez console.log()
sa valeur immédiatement après l'avoir changé et voir qu'elle a {[20] } été modifiée ( ce qui montre pourquoi L'explication de DVK N'est pas correcte) - le navigateur attend que la pile épuisez (la fonction de gestionnaire on
à retourner) et donc le message à terminer, de sorte qu'il puisse éventuellement exécuter le message qui a été ajouté par l'exécution en réaction à notre opération de mutation, et afin de refléter cette mutation dans L'interface utilisateur.
C'est parce que nous attendons que le code se termine. Nous n'avons pas dit "quelqu'un va chercher ceci et appelle ensuite cette fonction avec les résultats, Merci, et maintenant j'ai fini donc imma return, fais n'importe quoi maintenant", comme nous habituellement faire avec notre Javascript asynchrone basé sur les événements. Nous entrons dans une fonction de gestionnaire d'événements de clic, nous mettons à jour un élément DOM, nous appelons une autre fonction, l'autre fonction fonctionne longtemps et retourne ensuite, nous mettons à jour le même élément DOM, et puis nous revenons de la fonction initiale, vidant efficacement la pile. Et alors le navigateur peut accéder au message suivant dans la file d'attente, qui pourrait très bien être un message généré par nous en déclenchant des événement de type" on-DOM-mutation".
L'interface utilisateur du navigateur ne peut pas (ou choisit de ne pas) mettre à jour l'interface utilisateur tant que le cadre en cours d'exécution n'est pas terminé (la fonction est retournée). Personnellement, je pense que c'est plutôt par conception que par restriction.
Pourquoi la chose setTimeout
fonctionne-t-elle alors? Il le fait, car il supprime efficacement l'appel à la fonction de longue durée de son propre cadre, en le programmant pour être exécuté plus tard dans le contexte window
, de sorte qu'il puisse lui-même retourner immédiatement et autorisez la file d'attente de messages à traiter d'autres messages. Et l'idée est que le message "on update" de L'interface utilisateur qui a été déclenché par nous en Javascript lors de la modification du texte dans le DOM est maintenant en avance sur le message mis en file d'attente pour la fonction de longue durée, de sorte que la mise à jour de l'interface utilisateur se produit avant
Notez que a) la fonction de longue durée bloque toujours tout quand elle s'exécute, et b) vous n'êtes pas assuré que la mise à jour de L'interface utilisateur est réellement en avance sur dans la file d'attente de messages. Sur mon navigateur Chrome de juin 2018, une valeur de 0
ne "résout" pas le problème que le violon démontre - 10 le fait. Je suis en fait un peu étouffé par cela, car il me semble logique que le message de mise à jour de L'interface utilisateur soit mis en file d'attente avant, puisque son déclencheur est exécuté avant de planifier la fonction de longue durée à exécuter "plus tard". Mais peut-être y a-t-il des optimisations dans le moteur V8 qui peuvent interférer, ou peut-être que ma compréhension manque juste.
OK, alors Quel est le problème avec l'utilisation de setTimeout
, et quelle est la meilleure solution pour ce cas particulier?
Tout d'Abord, le problème de l'utilisation de setTimeout
sur n'importe quel gestionnaire d'événement comme ça, pour essayer d'atténuer un autre problème, est enclin à jouer avec l'autre code. Voici un exemple réel de mon travail:
Un collègue, dans une compréhension mal informée de la boucle d'événement, a essayé de" thread " Javascript en faisant utiliser du code de rendu de modèle setTimeout 0
pour son rendu. Il n'est plus là pour demander, mais je peut supposer qu'il a peut-être inséré des minuteries pour évaluer la vitesse de rendu (qui serait l'immédiateté de retour des fonctions) et a constaté que l'utilisation de cette approche ferait des réponses extrêmement rapides de cette fonction.
Le premier problème est évident; vous ne pouvez pas enfiler javascript, donc vous ne gagnez rien ici pendant que vous ajoutez de l'obscurcissement. Deuxièmement, vous avez maintenant effectivement détaché le rendu d'un modèle de la pile d'écouteurs d'événements possibles qui pourraient s'attendre à ce que ce modèle ont été rendus, alors qu'il peut très bien ne pas avoir été. Le comportement réel de cette fonction était maintenant non déterministe, comme l'était-sans le savoir-toute fonction qui l'exécuterait ou en dépendrait. Vous pouvez faire des suppositions éclairées, mais vous ne pouvez pas coder correctement son comportement.
Le "fix" lors de l'écriture d'un nouveau gestionnaire d'événement qui dépendait de sa logique était de aussi utiliser setTimeout 0
. Mais, ce n'est pas une solution, c'est difficile à comprendre, et il n'est pas drôle pour déboguer les erreurs causées par ce type de code. Parfois, il n'y a jamais de problème, d'autres fois, il échoue de manière concise, et là encore, parfois cela fonctionne et se casse sporadiquement, en fonction des performances actuelles de la plate-forme et de tout ce qui se passe à ce moment-là. C'est pourquoi je déconseille personnellement d'utiliser ce hack (c'est {[20] } un hack, et nous devrions tous le savoir), à moins que vous ne sachiez vraiment ce que vous faites et quelles sont les conséquences.
, Mais ce que peut, nous ne au lieu de cela? Eh bien, comme le suggère L'article MDN référencé, divisez le travail en plusieurs messages (si vous le pouvez) afin que les autres messages mis en file d'attente puissent être entrelacés avec votre travail et exécutés pendant son exécution, ou utilisez un web worker, qui peut fonctionner en tandem avec votre page et renvoyer des résultats lorsque vous avez terminé avec ses calculs.
Oh, et si vous pensez, "Eh bien, ne pourrais-je pas simplement mettre un rappel dans la fonction de longue durée pour le rendre asynchrone?"et puis non. Le rappel ne fait pas il est asynchrone, il devra toujours exécuter le code de longue durée avant d'appeler explicitement votre rappel.
Javascript est une application mono-thread de sorte que ne permettent pas d'exécuter la fonction simultanément afin d'atteindre cet événement, les boucles sont utilisées. Donc, exactement ce que setTimeout (fn, 0) fait que son pussed dans la quête de tâche qui est exécutée lorsque votre pile d'appels est vide. Je sais que cette explication est assez ennuyeux, donc je vous recommande de passer par cette vidéo cela vous aidera comment les choses fonctionnent sous le capot dans le navigateur. Découvrez cette vidéo:- https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ
Le problème était que vous essayiez d'effectuer une opération Javascript sur un élément non existant. L'élément devait encore être chargé et setTimeout()
donne plus de temps pour un élément à charger de la manière suivante:
-
setTimeout()
provoque l'événement ansynchronous donc exécuté après tout le code synchrone, donnant à votre élément plus de temps à charger. Rappels asynchrones comme le rappel danssetTimeout()
sont placés dans le file d'attente d'événements et mis sur la pile par l' boucle d'événement après la pile de code synchrone est vide. - La valeur 0 pour ms comme second argument de la fonction
setTimeout()
est souvent légèrement plus élevée (4-10 ms selon le navigateur). Ce temps légèrement supérieur nécessaire à l'exécution des rappelssetTimeout()
est causé par la quantité de 'ticks' (où une tique pousse un rappel sur la pile si la pile est vide) de la boucle d'événement. Pour des raisons de performances et de durée de vie de la batterie la quantité de ticks dans la boucle d'événement est limitée à un certain Quantité moins que 1000 fois par seconde.