Quiero mostrar un GIF animado (icono giratorio) durante la ejecución de una función y luego ocultar el GIF cuando se complete la función.
Sin embargo, el código que estoy implementando aparentemente no muestra el GIF porque la función está acaparando el hilo y no permite que se muestre el GIF.
Ejemplo: ( https://jsfiddle.net/jzxu8cLt/ )
HTML <button onclick="go();">Go</button> <span id="result"></span> <hr> <img style="display: none;" id="spinner" src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif"> JS function longLoop() { let count = 1000000000; let result = 0; for (let i = 1; i <= count; i++) { result = i; } return result; } function go() { let spinnerElement = document.getElementById('spinner'); let resultElement = document.getElementById('result'); spinnerElement.style.display = 'block'; let result = longLoop(); resultElement.innerHTML = result; spinnerElement.style.display = 'none'; }La función go() anterior debería hacer el trabajo de mostrar el GIF antes de llamar a una función de ejecución prolongada, luego ocultará el GIF cuando se complete la función de ejecución prolongada.
Sin embargo, el GIF nunca tiene la oportunidad de mostrarse en el navegador y no puedo averiguar cómo hacer que se muestre el GIF. Nota: si coloco un punto de interrupción en la línea que llama a la función longLoop(), el GIF se muestra correctamente.
¿Qué debo hacer para permitir que el GIF se muestre y se anime durante la función longLoop()?
Sí, tienes razón, dado que JavaScript tiene un solo subproceso, la función longLoop bloquea el bucle de eventos y no le da al navegador la oportunidad de mostrar el gif.
Solución JsFiddle: https://jsfiddle.net/12yf4tgd/
Usé setTimeout para hacer que la función longLoop fuera asíncrona y la envolví en una Promesa para que las otras asignaciones se ejecuten después de que se complete.
function go() { let spinnerElement = document.getElementById('spinner'); let resultElement = document.getElementById('result'); spinnerElement.style.display = 'block'; new Promise(resolve => setTimeout(() => { resolve(longLoop()) })) .then((result) => { spinnerElement.style.display = 'none'; resultElement.innerHTML = result; }) }Dado que está siendo manejado en otro lugar por alguna Web Api y no en la pila de llamadas principal, no se bloqueará.
Si no quiere usar .then() debido a todo el anidamiento, la otra opción es hacer go() y la función asíncrona para aprovechar la sintaxis asíncrona/espera.
async function go() { let spinnerElement = document.getElementById('spinner'); let resultElement = document.getElementById('result'); spinnerElement.style.display = 'block'; let result = await new Promise((resolve, reject) => { setTimeout(() => resolve(longLoop())) }) spinnerElement.style.display = 'none'; resultElement.innerHTML = result; }Ambos funcionan de la misma manera.
Ejecute su código de larga ejecución y reversión en un setTimeout con parámetro de tiempo omitido o 0 que ejecutará "inmediatamente", o más precisamente, el próximo ciclo de eventos, lo que evitará el bloqueo.
function longLoop() { let count = 1000000000; let result = 0; for (let i = 1; i <= count; i++) { result = i; } return result; } function go() { let spinnerElement = document.getElementById('spinner'); let resultElement = document.getElementById('result'); spinnerElement.style.display = 'block'; setTimeout(() => { let result = longLoop(); resultElement.innerHTML = result; spinnerElement.style.display = 'none'; }) } <button onclick="go();">Go</button> <span id="result"></span> <hr> <img style="display: none;" id="spinner" src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif">Recomendaré el uso de async/await para que el código sea más legible aquí y las soluciones:
function longLoop(timeout) { return new Promise(resolve => setTimeout(resolve, timeout)); } async function go() { let spinnerElement = document.getElementById('spinner'); let resultElement = document.getElementById('result'); spinnerElement.style.display = 'block'; let result = await longLoop(4000); spinnerElement.style.display = 'none'; }