¿Por qué setTimeout (fn, 0) a veces es útil?

Recientemente me encontré con un error bastante desagradable, en el que el código cargaba un <select> dinámicamente a través de JavaScript. Esta cargada dinámicamente <select> tenía un valor preseleccionado. En IE6, ya teníamos código para arreglar el seleccionado <option>, porque a veces el <select>'s selectedIndex el valor no estaría sincronizado con el seleccionado <option>'s index atributo, como se muestra a continuación:

field.selectedIndex = element.index;

Sin embargo, este código no funcionaba. Aunque el campo selectedIndex se estaba configurando correctamente, se terminaría seleccionando el índice incorrecto. Sin embargo, si pegué un alert() declaración en el momento adecuado, se seleccionaría la opción correcta. Pensando que esto podría ser algún tipo de problema de sincronización, probé algo aleatorio que había visto antes en el código:

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

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

¡Y esto funcionó!

Tengo una solución para mi problema, pero me inquieta no saber exactamente por qué esto soluciona mi problema. ¿Alguien tiene una explicación oficial? ¿Qué problema con el navegador estoy evitando al llamar a mi función "más tarde" usando setTimeout()?

preguntado el 22 de abril de 09 a las 18:04

La mayoría de las preguntas describen por qué es útil. Si necesita saber por qué sucede esto, lea mi respuesta: stackoverflow.com/a/23747597/1090562 -

Philip Roberts explica esto de la mejor manera posible aquí en su charla "¿Qué diablos es el ciclo de eventos?" youtube.com/watch?v=8aGhZQkoFbQ -

Si tiene prisa, esta es la parte del video en la que comienza a abordar la pregunta exactamente: youtu.be/8aGhZQkoFbQ?t=14m54s. Indudablemente, vale la pena ver todo el video. -

setTimeout(fn) es igual que setTimeout(fn, 0), por cierto. -

17 Respuestas

En la pregunta, existía un condición de carrera Entre:

  1. El intento del navegador de inicializar la lista desplegable, listo para actualizar su índice seleccionado, y
  2. Tu código para configurar el índice seleccionado

Su código ganaba constantemente esta carrera e intentaba establecer la selección desplegable antes de que el navegador estuviera listo, lo que significa que aparecería el error.

Esta carrera existió porque JavaScript tiene una hilo único de ejecución que se comparte con la representación de páginas. De hecho, ejecutar JavaScript bloquea la actualización del DOM.

Tu solución alternativa fue:

setTimeout(callback, 0)

Invocando setTimeout con una devolución de llamada, y cero como segundo argumento programará la devolución de llamada para que se ejecute de forma asíncrona, después de la demora más corta posible, que será de alrededor de 10 ms cuando la pestaña tenga el foco y el hilo de ejecución de JavaScript no esté ocupado.

La solución del OP, por lo tanto, fue retrasar unos 10 ms, la configuración del índice seleccionado. Esto le dio al navegador la oportunidad de inicializar el DOM, solucionando el error.

Todas las versiones de Internet Explorer mostraban comportamientos extravagantes y, en ocasiones, este tipo de solución era necesaria. Alternativamente, podría haber sido un error genuino en la base de código del OP.


Vea la charla de Philip Roberts "¿Qué diablos es el bucle de eventos?" para una explicación más detallada.

Respondido 24 Feb 20, 01:02

"La solución es" pausar "la ejecución de JavaScript para permitir que los subprocesos de renderizado se pongan al día." No es del todo cierto, lo que hace setTimeout es agregar un nuevo evento a la cola de eventos del navegador y el motor de renderizado ya está en esa cola (no es del todo cierto, pero lo suficientemente cerca) por lo que se ejecuta antes del evento setTimeout. - david mulder

Sí, esa es una respuesta mucho más detallada y mucho más correcta. Pero el mío es "lo suficientemente correcto" para que la gente entienda por qué funciona el truco. - estático

@DavidMulder, ¿significa que el navegador analiza css y renderiza en un hilo diferente del hilo de ejecución de javascript? - Jason

No, se analizan en principio en el mismo hilo, de lo contrario, unas pocas líneas de manipulación DOM desencadenarían reflujos todo el tiempo, lo que tendría una influencia extremadamente mala en la velocidad de ejecución. - david mulder

Este video es la mejor explicación de por qué establecemosTimeout 0 2014.jsconf.eu/speakers/… - davibq

Prefacio:

Algunas de las otras respuestas son correctas, pero en realidad no ilustran cuál es el problema que se está resolviendo, así que creé esta respuesta para presentar esa ilustración detallada.

Como tal, estoy publicando un recorrido detallado de lo que hace el navegador y cómo se usa setTimeout() ayuda. Parece alargado, pero en realidad es muy simple y directo: lo hice muy detallado.

ACTUALIZACIÓN: He hecho un JSFiddle para demostrar en vivo la explicación a continuación: http://jsfiddle.net/C2YBE/31/ . Muchos gracias a @ThangChung por ayudar a ponerlo en marcha.

UPDATE2: En caso de que el sitio web JSFiddle muera o elimine el código, agregué el código a esta respuesta al final.


DETALLES:

Imagine una aplicación web con un botón "hacer algo" y un div de resultado.

La onClick El controlador del botón "hacer algo" llama a una función "LongCalc ()", que hace 2 cosas:

  1. Hace un cálculo muy largo (digamos que tarda 3 minutos)

  2. Imprime los resultados del cálculo en el div de resultado.

Ahora, sus usuarios comienzan a probar esto, hacen clic en el botón "hacer algo", y la página permanece allí aparentemente sin hacer nada durante 3 minutos, se ponen inquietos, hacen clic en el botón nuevamente, esperen 1 minuto, no pasa nada, hacen clic en el botón nuevamente ...

El problema es obvio: desea un DIV de "Estado", que muestre lo que está sucediendo. Veamos cómo funciona.


Por lo tanto, agrega un DIV de "Estado" (inicialmente vacío) y modifica el onclick handler (función LongCalc()) para hacer 4 cosas:

  1. Complete el estado "Calculando ... puede tardar ~ 3 minutos" en el estado DIV

  2. Hace un cálculo muy largo (digamos que tarda 3 minutos)

  3. Imprime los resultados del cálculo en el div de resultado.

  4. Complete el estado "Cálculo realizado" en el estado DIV

Y, felizmente, les da la aplicación a los usuarios para que la vuelvan a probar.

Vuelven a ti luciendo muy enojados. Y explíqueles que cuando hicieron clic en el botón, el estado DIV nunca se actualizó con el estado "Calculando ..." !!!


Te rascas la cabeza, preguntas en StackOverflow (o lees documentos o google) y te das cuenta del problema:

El navegador coloca todas sus tareas "TODO" (tanto las tareas de la interfaz de usuario como los comandos de JavaScript) resultantes de eventos en un cola única. Y desafortunadamente, volver a dibujar el DIV de "Estado" con el nuevo valor "Calculando ..." es un TODO separado que llega al final de la cola.

Aquí hay un desglose de los eventos durante la prueba de su usuario, el contenido de la cola después de cada evento:

  • Cola: [Empty]
  • Evento: haga clic en el botón. Cola después del evento: [Execute OnClick handler(lines 1-4)]
  • Evento: Ejecute la primera línea en el controlador OnClick (por ejemplo, cambie el valor DIV de estado). Cola después del evento: [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]. Tenga en cuenta que, si bien los cambios de DOM ocurren instantáneamente, para volver a dibujar el elemento DOM correspondiente, necesita un nuevo evento, desencadenado por el cambio de DOM, que fue al final de la cola..
  • ¡¡¡PROBLEMA!!! ¡¡¡PROBLEMA!!! Los detalles se explican a continuación.
  • Evento: Ejecute la segunda línea en el controlador (cálculo). Cola después de: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value].
  • Evento: Ejecute la tercera línea en el controlador (complete el resultado DIV). Cola después de: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result].
  • Evento: Ejecute la cuarta línea en el controlador (complete el estado DIV con "DONE"). Cola: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value].
  • Evento: ejecutar implícito return del onclick controlador sub. Sacamos el "controlador de ejecución de OnClick" de la cola y comenzamos a ejecutar el siguiente elemento de la cola.
  • NOTA: Como ya terminamos el cálculo, ya pasaron 3 minutos para el usuario. ¡El evento de re-sorteo aún no sucedió!
  • Evento: vuelva a dibujar el estado DIV con el valor "Calculando". Hacemos el nuevo dibujo y lo sacamos de la cola.
  • Evento: vuelva a dibujar el resultado DIV con el valor del resultado. Hacemos el nuevo dibujo y lo sacamos de la cola.
  • Evento: vuelva a dibujar el estado DIV con el valor "Listo". Hacemos el nuevo dibujo y lo sacamos de la cola. Los espectadores con ojos agudos pueden incluso notar "Estado DIV con el valor" Calculando "parpadeando durante una fracción de microsegundo - DESPUÉS DE TERMINAR EL CÁLCULO

Por lo tanto, el problema subyacente es que el evento de re-dibujar para "Estado" DIV se coloca en la cola al final, DESPUÉS del evento "ejecutar línea 2" que tarda 3 minutos, por lo que el re-dibujo real no ocurre hasta DESPUÉS de que se haya realizado el cálculo.


Al rescate viene el setTimeout(). ¿Cómo te ayuda? Porque al llamar al código de ejecución prolongada a través de setTimeout, en realidad creas 2 eventos: setTimeout ejecución en sí, y (debido al tiempo de espera 0), entrada de cola separada para el código que se está ejecutando.

Entonces, para solucionar su problema, modifique su onClick manejador para ser DOS declaraciones (en una nueva función o simplemente un bloque dentro onClick):

  1. Complete el estado "Calculando ... puede tardar ~ 3 minutos" en el estado DIV

  2. Implementación setTimeout() con 0 tiempo de espera y una llamada a LongCalc() función.

    LongCalc() la función es casi la misma que la última vez, pero obviamente no tiene la actualización DIV de estado "Calculando ..." como primer paso; y en su lugar comienza el cálculo de inmediato.

Entonces, ¿cómo se ven ahora la secuencia de eventos y la cola?

  • Cola: [Empty]
  • Evento: haga clic en el botón. Cola después del evento: [Execute OnClick handler(status update, setTimeout() call)]
  • Evento: Ejecute la primera línea en el controlador OnClick (por ejemplo, cambie el valor DIV de estado). Cola después del evento: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value].
  • Evento: ejecutar la segunda línea en el controlador (llamada setTimeout). Cola después de: [re-draw Status DIV with "Calculating" value]. La cola no tiene nada nuevo durante 0 segundos más.
  • Evento: La alarma del tiempo de espera se apaga, 0 segundos después. Cola después de: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)].
  • Evento: volver a dibujar el estado DIV con el valor "Calculando". Cola después de: [execute LongCalc (lines 1-3)]. Tenga en cuenta que este evento de re-dibujo podría suceder ANTES de que suene la alarma, lo que funciona igual de bien.
  • ...

¡Hurra! El DIV de estado se actualizó a "Calculando ..." antes de que comenzara el cálculo.



A continuación se muestra el código de muestra de JSFiddle que ilustra estos ejemplos: http://jsfiddle.net/C2YBE/31/ :

Código 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>

Código JavaScript: (ejecutado en onDomReady y puede requerir 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);
});

Respondido 21 Feb 20, 11:02

gran respuesta DVK! Aquí hay una esencia que ilustra tu ejemplo. gist.github.com/kumikoda/5552511#file-timeout-html - kumikoda

Respuesta realmente genial, DVK. Solo para que sea fácil de imaginar, he puesto ese código en jsfiddle jsfiddle.net/thangchung/LVAaV - gracias

@ThangChung - Intenté hacer una mejor versión (2 botones, uno para cada caso) en JSFiddle. Funciona como una demostración en Chrome e IE, pero no en FF por alguna razón; consulte jsfiddle.net/C2YBE/31. Pregunté por qué FF no funciona aquí: stackoverflow.com/questions/20747591/… - DVK

@DVK "El navegador coloca todas sus tareas" TODO "(tanto las tareas de la interfaz de usuario como los comandos de JavaScript) resultantes de los eventos en una sola cola". Señor, ¿puede proporcionar la fuente de esto? En mi opinión, no se supone que los navegadores tengan una interfaz de usuario (motor de renderizado) y subprocesos JS diferentes ... sin ofender ... solo quiero aprender ... bhavya_w

@bhavya_w No, todo sucede en un hilo. Esta es la razón por la que un cálculo js largo puede bloquear la interfaz de usuario: Seba Kerckhof

Eche un vistazo al artículo de John Resig sobre Cómo funcionan los temporizadores de JavaScript. Cuando establece un tiempo de espera, en realidad pone en cola el código asincrónico hasta que el motor ejecuta la pila de llamadas actual.

Respondido el 06 de diciembre de 10 a las 21:12

Los navegadores tienen un proceso llamado "hilo principal", que es responsable de ejecutar algunas tareas de JavaScript, actualizaciones de la interfaz de usuario, por ejemplo: pintar, redibujar, redistribuir, etc. Las tareas de JavaScript se colocan en una cola de mensajes y luego se envían al hilo principal del navegador para ser ejecutado. Cuando se generan actualizaciones de la interfaz de usuario mientras el hilo principal está ocupado, las tareas se agregan a la cola de mensajes.

Respondido 05 ago 20, 17:08

"Cada ejecución de JavaScript y las tareas de actualización de la interfaz de usuario se agregan al sistema de cola de eventos del navegador, luego esas tareas se envían al subproceso de la interfaz de usuario principal del navegador para que se realice" ... ¿fuente, por favor? - bhavya_w

JavaScript de alto rendimiento (Nicholas Zakas, Stoyan Stefanov, Ross Harmes, Julien Lecomte y Matt Sweeney) - Arley

Votar en contra para esto add this fn to the end of the queue. Lo más importante es dónde exactamente setTimeout agrega esta función, el final de este ciclo de bucle o el comienzo del siguiente ciclo de bucle. - Verde

setTimeout() le compra algo de tiempo hasta que se cargan los elementos DOM, incluso si se establece en 0.

Miren esto: setTimeout

Respondido 22 Abr '09, 22:04

Aquí hay respuestas contradictorias con votos a favor, y sin pruebas no hay forma de saber en quién creer. Aquí hay una prueba de que @DVK tiene razón y @SalvadorDali es incorrecto. Este último afirma:

"Y esta es la razón: no es posible tener setTimeout con un retraso de tiempo de 0 milisegundos. El valor mínimo lo determina el navegador y no es 0 milisegundos. Históricamente, los navegadores establecen este mínimo en 10 milisegundos, pero las especificaciones HTML5 y los navegadores modernos lo tienen configurado en 4 milisegundos ".

El tiempo de espera mínimo de 4 ms es irrelevante para lo que está sucediendo. Lo que realmente sucede es que setTimeout empuja la función de devolución de llamada al final de la cola de ejecución. Si después de setTimeout (devolución de llamada, 0) tiene un código de bloqueo que tarda varios segundos en ejecutarse, la devolución de llamada no se ejecutará durante varios segundos, hasta que el código de bloqueo haya finalizado. Prueba este código:

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 salida es:

setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033

Respondido el 13 de Septiembre de 19 a las 00:09

tu respuesta no prueba nada. Simplemente muestra que en su máquina en una situación específica, la computadora le arroja algunos números. Para probar algo relacionado, necesita un poco más de unas pocas líneas de código y algunos números. - Salvador Dalí

@SalvadorDali, creo que mi prueba es lo suficientemente clara para que la mayoría de la gente la entienda. Creo que te sientes a la defensiva y no has hecho el esfuerzo de entenderlo. Estaré feliz de intentar aclararlo, pero no sé qué es lo que no entiendes. Intente ejecutar el código en su propia máquina si sospecha de mis resultados. - vladimir corea

Intentaré dejarlo claro por última vez. Mi respuesta es aclarar algunas de las propiedades interesantes en el tiempo de espera, el intervalo y está bien respaldado por fuentes válidas y reconocibles. Hizo una fuerte afirmación de que está mal y señaló su código que, por alguna razón, nombró una prueba. No hay nada de malo en tu código (no estoy en contra). Solo digo que mi respuesta no es incorrecta. Aclara algunos puntos. Voy a detener esta guerra, porque no veo sentido. - Salvador Dalí

Una razón para hacerlo es posponer la ejecución del código a un ciclo de eventos posterior separado. Al responder a un evento del navegador de algún tipo (clic del mouse, por ejemplo), a veces es necesario realizar solo operaciones después de se procesa el evento actual. La setTimeout() La facilidad es la forma más sencilla de hacerlo.

editar ahora que estamos en 2015, debo tener en cuenta que también hay requestAnimationFrame(), que no es exactamente lo mismo, pero está lo suficientemente cerca de setTimeout(fn, 0) que vale la pena mencionar.

Respondido 22 Oct 15, 13:10

Este es precisamente uno de los lugares donde he visto que se usa. =) - Zaibot

FYI: esta respuesta se fusionó aquí desde stackoverflow.com/questions/4574940/… - shog9

es diferir la ejecución del código a un ciclo de eventos subsiguiente separado: ¿cómo averiguas un bucle de eventos subsiguientes? ¿Cómo averiguas qué es un bucle de eventos actual? ¿Cómo sabes en qué bucle de eventos te encuentras ahora? - Verde

@Green, bueno, realmente no; realmente no hay visibilidad directa de lo que está haciendo el tiempo de ejecución de JavaScript. - Puntiagudo

requestAnimationFrame resolvió el problema que tenía con IE y Firefox no actualizando la interfaz de usuario a veces - David

Ambas de estas dos respuestas mejor calificadas son incorrectas. Consulte la descripción de MDN sobre el modelo de simultaneidad y el bucle de eventos., y debería quedar claro lo que está pasando (ese recurso MDN es una verdadera joya). Y simplemente usando setTimeout puede agregar problemas inesperados en su código además de "resolver" este pequeño problema.

Qué está haciendo Realmente Lo que sucede aquí no es que "el navegador no esté listo todavía debido a la simultaneidad", o algo basado en "cada línea es un evento que se agrega al final de la cola".

La jsFiddle proporcionado por DVK de hecho ilustra un problema, pero su explicación no es correcta.

Lo que sucede en su código es que primero adjunta un controlador de eventos al click evento en el #do

Luego, cuando realmente hace clic en el botón, un message se crea haciendo referencia a la función del controlador de eventos, que se agrega a la message queue. Cuando el event loop llega a este mensaje, crea un frame en la pila, con la llamada de función al controlador de eventos click en jsfiddle.

Y aquí es donde se pone interesante. Estamos tan acostumbrados a pensar que Javascript es asincrónico que somos propensos a pasar por alto este pequeño hecho: Cualquier fotograma debe ejecutarse, en su totalidad, antes de que se pueda ejecutar el siguiente fotograma.. Sin concurrencia, gente.

¿Qué significa esto? Significa que cada vez que se invoca una función desde la cola de mensajes, bloquea la cola hasta que se vacía la pila que genera. O, en términos más generales, bloquea hasta que la función ha regresado. Y bloquea todo, incluidas las operaciones de renderizado DOM, el desplazamiento y todo eso. Si desea confirmación, simplemente intente aumentar la duración de la operación de ejecución prolongada en el violín (por ejemplo, ejecute el bucle externo 10 veces más) y notará que mientras se ejecuta, no puede desplazarse por la página. Si se ejecuta el tiempo suficiente, su navegador le preguntará si desea finalizar el proceso, porque hace que la página no responda. La trama se está ejecutando y el bucle de eventos y la cola de mensajes se bloquean hasta que finaliza.

Entonces, ¿por qué este efecto secundario del texto no se actualiza? Porque mientras tu tienen cambió el valor del elemento en el DOM - puede console.log() su valor inmediatamente después de cambiarlo y ver que tiene ha cambiado (lo que muestra por qué la explicación de DVK no es correcta): el navegador está esperando que la pila se agote (el on handler function to return) y, por lo tanto, el mensaje para terminar, para que eventualmente pueda ejecutar el mensaje que ha sido agregado por el tiempo de ejecución como una reacción a nuestra operación de mutación, y para reflejar esa mutación en la interfaz de usuario.

Esto se debe a que en realidad estamos esperando que el código termine de ejecutarse. No hemos dicho "alguien busque esto y luego llame a esta función con los resultados, gracias, y ahora he terminado, así que voy a volver, hacer lo que sea ahora", como solemos hacer con nuestro Javascript asíncrono basado en eventos. Ingresamos una función de controlador de eventos de clic, actualizamos un elemento DOM, llamamos a otra función, la otra función funciona durante mucho tiempo y luego regresa, luego actualizamos el mismo elemento DOM, y luego volvemos de la función inicial, vaciando efectivamente la pila. Y luego el navegador puede llegar al siguiente mensaje de la cola, que muy bien podría ser un mensaje generado por nosotros al desencadenar algún evento interno de tipo "on-DOM-mutation".

La interfaz de usuario del navegador no puede (o elige no hacerlo) actualizar la interfaz de usuario hasta que el marco que se está ejecutando actualmente se haya completado (la función ha regresado). Personalmente, creo que esto es más por diseño que por restricción.

¿Por qué el setTimeout ¿Funciona entonces? Lo hace porque elimina efectivamente la llamada a la función de larga duración de su propio marco, programándola para que se ejecute más tarde en el window contexto, de modo que él mismo pueda regresa inmediatamente y permitir que la cola de mensajes procese otros mensajes. Y la idea es que el mensaje de IU "en actualización" que activamos en Javascript al cambiar el texto en el DOM ahora está por delante del mensaje en cola para la función de larga duración, de modo que la actualización de la IU ocurre antes de que bloqueemos por mucho tiempo.

Tenga en cuenta que a) La función de larga duración todavía bloquea todo cuando se ejecuta, yb) no se le garantiza que la actualización de la interfaz de usuario esté realmente por delante en la cola de mensajes. En mi navegador Chrome de junio de 2018, un valor de 0 no "arregla" el problema que demuestra el violín - 10 lo hace. De hecho, esto me asfixia un poco, porque me parece lógico que el mensaje de actualización de la interfaz de usuario se ponga en cola antes que él, ya que su disparador se ejecuta antes de programar la función de larga duración para que se ejecute "más tarde". Pero tal vez haya algunas optimizaciones en el motor V8 que puedan interferir, o tal vez mi comprensión sea deficiente.

De acuerdo, ¿cuál es el problema con el uso setTimeouty ¿cuál es la mejor solución para este caso en particular?

En primer lugar, el problema de usar setTimeout en cualquier controlador de eventos como este, para tratar de aliviar otro problema, es propenso a meterse con otro código. Aquí hay un ejemplo de la vida real de mi trabajo:

Un colega, en un entendimiento mal informado sobre el bucle de eventos, intentó "enhebrar" Javascript usando algún código de representación de plantilla setTimeout 0 por su interpretación. Ya no está aquí para preguntar, pero puedo suponer que tal vez insertó temporizadores para medir la velocidad de renderizado (que sería la inmediatez de retorno de las funciones) y descubrió que usar este enfoque generaría respuestas increíblemente rápidas de esa función.

El primer problema es obvio; no puede subprocesar javascript, por lo que no gana nada aquí mientras agrega ofuscación. En segundo lugar, ahora ha separado efectivamente la representación de una plantilla de la pila de posibles detectores de eventos que podrían esperar que esa misma plantilla se haya representado, mientras que es muy posible que no lo haya sido. El comportamiento real de esa función ahora no era determinista, al igual que, sin saberlo, cualquier función que la ejecutara o dependiera de ella. Puede hacer conjeturas fundamentadas, pero no puede codificar correctamente su comportamiento.

La "solución" al escribir un nuevo controlador de eventos que dependía de su lógica era Además, utilizado setTimeout 0. Pero eso no es una solución, es difícil de entender y no es divertido depurar errores causados ​​por un código como este. A veces no hay ningún problema, otras veces falla de manera constante y, de nuevo, a veces funciona y se rompe esporádicamente, según el rendimiento actual de la plataforma y cualquier otra cosa que suceda en ese momento. Es por eso que personalmente desaconsejaría el uso de este truco (es is un truco, y todos deberíamos saber que lo es), a menos que realmente sepa lo que está haciendo y cuáles son las consecuencias.

Pero que puede hacemos en su lugar? Bueno, como sugiere el artículo de MDN al que se hace referencia, divida el trabajo en varios mensajes (si puede) para que otros mensajes que están en cola puedan intercalarse con su trabajo y ejecutarse mientras se ejecuta, o use un trabajador web, que puede en conjunto con su página y devuelva resultados cuando termine con sus cálculos.

Ah, y si estás pensando, "Bueno, ¿no podría simplemente poner una devolución de llamada en la función de larga duración para hacerla asincrónica?", Entonces no. La devolución de llamada no la hace asincrónica, aún tendrá que ejecutar el código de larga duración antes de llamar explícitamente a su devolución de llamada.

Respondido el 05 de junio de 18 a las 13:06

Aparentemente, el único comentario válido y completo en toda esta página: rottweilers_anonimo

Esta es una pregunta antigua con respuestas antiguas. Quería agregar una nueva mirada a este problema y responder por qué sucede esto y no por qué es útil.

Entonces tienes dos funciones:

var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

var f2 = function () {
    console.log("f2", "Second call...");
};

y luego llámalos en el siguiente orden f1(); f2(); solo para ver que el segundo se ejecutó primero.

Y he aquí por qué: no es posible tener setTimeout con un retardo de tiempo de 0 milisegundos. La El valor mínimo lo determina el navegador. y no es 0 milisegundos. Históricamente navegadores establecen este mínimo en 10 milisegundos, pero el Especificaciones HTML5 y los navegadores modernos lo establecen en 4 milisegundos.

Si el nivel de anidamiento es superior a 5 y el tiempo de espera es inferior a 4, aumente el tiempo de espera a 4.

También de mozilla:

Para implementar un tiempo de espera de 0 ms en un navegador moderno, puede usar window.postMessage () como se describe aquí.

La información de PS se toma después de leer lo siguiente artículo.

contestado el 19 de mayo de 14 a las 22:05

@ user2407309 ¿Estás bromeando? ¿Quiere decir que la especificación HTML5 es incorrecta y está en lo correcto? Lea las fuentes antes de votar negativamente y hacer afirmaciones sólidas. Mi respuesta se basa en la especificación HTML y el registro histórico. En lugar de hacer la respuesta que explica completamente lo mismo una y otra vez, agregué algo nuevo, algo que no se mostró en las respuestas anteriores. No estoy diciendo que esta sea la única razón, solo estoy mostrando algo nuevo. - Salvador Dalí

Esto es incorrecto: "Y aquí está la razón: no es posible tener setTimeout con un retardo de tiempo de 0 milisegundos". No es por eso. El retraso de 4 ms es irrelevante para el por qué setTimeout(fn,0) es útil. - vladimir corea

@ user2407309 se puede modificar fácilmente a "para agregar a las razones indicadas por otros, no es posible ...". Por lo tanto, es ridículo votar en contra solo por esto, especialmente si su propia respuesta no dice nada nuevo. Solo una pequeña edición sería suficiente. - Salvador Dalí

Salvador Dali: Si ignoras los aspectos emocionales de la guerra de micro llamas aquí, probablemente tendrías que admitir que @VladimirKornea tiene razón. Es cierto que los navegadores asignan un retraso de 0 ms a 4 ms, pero incluso si no lo hicieran, los resultados seguirían siendo los mismos. El mecanismo de conducción aquí es que el código se inserta en la cola, en lugar de la pila de llamadas. Eche un vistazo a esta excelente presentación de JSConf, puede ayudar a aclarar el problema: youtube.com/watch?v=8aGhZQkoFbQ - cambio hash

Estoy confundido en cuanto a por qué cree que su cotización en un mínimo calificado de 4 ms es un mínimo global de 4 ms. Como muestra su cita de la especificación HTML5, el mínimo es 4 ms solo cuando haya anidado llamadas a setTimeout/setInterval más de cinco niveles de profundidad; si no lo ha hecho, el mínimo es 0 ms (con la falta de máquinas del tiempo). Los documentos de Mozilla amplían eso para cubrir casos repetidos, no solo anidados (por lo que setInterval con intervalo de 0 se reprogramará inmediatamente unas cuantas veces, luego se retrasará más después de eso), pero los usos simples de setTimeout con anidamiento mínimo se les permite hacer cola inmediatamente. - ShadowRanger

Dado que se está pasando una duración de 0, Supongo que es para eliminar el código pasado al setTimeout del flujo de ejecución. Entonces, si se trata de una función que podría demorar un tiempo, no evitará que se ejecute el código posterior.

Respondido el 01 de enero de 11 a las 17:01

FYI: esta respuesta se fusionó aquí desde stackoverflow.com/questions/4574940/… - shog9

La otra cosa que hace es empujar la invocación de la función al final de la pila, evitando un desbordamiento de la pila si está llamando a una función de forma recursiva. Esto tiene el efecto de una while bucle pero permite que el motor de JavaScript active otros temporizadores asincrónicos.

Respondido el 21 de junio de 12 a las 02:06

Votar en contra para esto push the function invocation to the bottom of the stack. ¿Qué stack de lo que estás hablando es oscuro. Lo más importante es dónde exactamente setTimeout agrega esta función, el final de este ciclo de bucle o el comienzo del siguiente ciclo de bucle. - Verde

El problema era que intentaba realizar una operación de Javascript en un elemento no existente. El elemento aún no se había cargado y setTimeout() da más tiempo para que un elemento se cargue de las siguientes maneras:

  1. setTimeout() hace que el evento sea ansincrónico por lo tanto, se ejecuta después de todo el código síncrono, lo que le da a su elemento más tiempo para cargar. Devoluciones de llamada asincrónicas como la devolución de llamada en setTimeout() se colocan en el cola de eventos y poner en la pila por el bucle de eventos después de que la pila de código síncrono esté vacía.
  2. El valor 0 para ms como segundo argumento en la función setTimeout() suele ser ligeramente superior (4-10 ms según el navegador). Este tiempo ligeramente mayor necesario para ejecutar el setTimeout() las devoluciones de llamada son causadas por la cantidad de 'ticks' (donde un tick empuja una devolución de llamada en la pila si la pila está vacía) del bucle de eventos. Debido a razones de rendimiento y duración de la batería, la cantidad de tics en el bucle de eventos está restringida a una cierta cantidad menos de 1000 veces por segundo.

Respondido el 15 de Septiembre de 18 a las 10:09

Al llamar a setTimeout, le da tiempo a la página para reaccionar ante lo que esté haciendo el usuario. Esto es particularmente útil para las funciones que se ejecutan durante la carga de la página.

Respondido 22 Abr '09, 22:04

Algunos otros casos en los que setTimeout es útil:

Desea dividir un ciclo o cálculo de larga duración en componentes más pequeños para que el navegador no parezca "congelarse" o decir "El script en la página está ocupado".

Desea deshabilitar un botón de envío de formulario cuando se hace clic en él, pero si deshabilita el botón en el controlador onClick, el formulario no se enviará. setTimeout con un tiempo de cero hace el truco, permitiendo que el evento termine, el formulario comience a enviarse, luego su botón se puede deshabilitar.

Respondido el 14 de diciembre de 12 a las 13:12

La desactivación se haría mejor en el evento onsubmit; sería más rápido y se garantiza que se llamará antes de que se envíe técnicamente el formulario, ya que puede detener el envío. - Kris

Muy cierto. Supongo que la desactivación al hacer clic es más fácil para la creación de prototipos porque simplemente puede escribir onclick = "this.disabled = true" en el botón, mientras que la desactivación al enviar requiere un poco más de trabajo. - fabspro

Las respuestas sobre los bucles de ejecución y la representación del DOM antes de que se complete algún otro código son correctas. Los tiempos de espera de cero segundos en JavaScript ayudan a que el código sea pseudo-multiproceso, aunque no lo sea.

Quiero agregar que el MEJOR valor para un tiempo de espera de cero segundos entre navegadores / plataformas cruzadas en JavaScript es en realidad de aproximadamente 20 milisegundos en lugar de 0 (cero), porque muchos navegadores móviles no pueden registrar tiempos de espera inferiores a 20 milisegundos debido a limitaciones del reloj en chips AMD.

Además, los procesos de larga ejecución que no implican la manipulación del DOM deben enviarse ahora a los Web Workers, ya que proporcionan una verdadera ejecución multiproceso de JavaScript.

respondido 13 mar '13, 23:03

Soy un poco escéptico acerca de su respuesta, pero la voté a favor porque me obligó a hacer una investigación adicional sobre los estándares del navegador. Cuando investigo estándares, voy a donde siempre voy, MDN: developer.mozilla.org/en-US/docs/Web/API/window.setTimeout La especificación HTML5 dice 4ms. No dice nada sobre las limitaciones de reloj en los chips móviles. Tener dificultades para buscar en Google una fuente de información para respaldar sus declaraciones. Descubrí que Dart Language de Google eliminó setTimeout por completo a favor de un objeto Timer. - Juan Zabroski

(...) porque muchos navegadores móviles no pueden registrar tiempos de espera menores a 20 milisegundos debido a limitaciones de reloj (...) Cada plataforma tiene limitaciones de tiempo debido a su reloj y ninguna plataforma es capaz de ejecutar la siguiente cosa exactamente 0 ms después del actual. El tiempo de espera de 0ms solicita la ejecución de una función lo antes posible y las limitaciones de tiempo de una plataforma específica no cambian el significado de esto de ninguna manera. - Piotr Dobrogost

setTimout on 0 también es muy útil en el patrón de configuración de una promesa diferida, que desea devolver de inmediato:

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();
}

Respondido el 02 de Septiembre de 15 a las 16:09

Javascript es una aplicación de un solo subproceso, por lo que no permite ejecutar la función al mismo tiempo, por lo que para lograr este evento se utilizan bucles. Entonces, exactamente lo que setTimeout (fn, 0) hace que se meta en la búsqueda de tareas que se ejecuta cuando su pila de llamadas está vacía. Sé que esta explicación es bastante aburrida, así que te recomiendo que veas este video que te ayudará a ver cómo funcionan las cosas bajo el capó en el navegador. Mira este video:- https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

respondido 02 mar '18, 07:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.