Tengo que ejecutar un código después de algunos intervalos irregulares predefinidos después de que los usuarios realicen alguna acción, como hacer clic en un botón.
Los intervalos pueden ser [1, 1.2, 2.05, 2.8, ...., 10.56]
, [0.2, 1.5, 3.9, 4.67, 6.8, .., 12.0]
o algo más. Por lo tanto, la función debe llamarse después de 1 segundo, 1,2 segundos, 2,05 segundos y así sucesivamente después de hacer clic en el botón inicial.
¿Cómo puedo hacer eso en JavaScript?
setTimeout
dentro de una función que es llamada por ese setTimeout
:
// intervals in seconds const intervals = [1, 1.2, 2.05, 2.8, 10.56]; (function runIrregularly(runThisCode) { if (intervals.length > 0) { // get (and remove) the first element from the array: const timeout = intervals.shift(); // and schedule the next call to run in the future setTimeout( () => runIrregularly(runThisCode), timeout * 1000 ); } // then run our real function runThisCode(); })(doSomething); function doSomething() { console.log(Date.now()); }
Ahora, mientras usamos shift()
para obtener elementos desde el inicio de la matriz, en ese momento debería decir "hm, ¿así que ni siquiera le importa que sea una matriz?" y de hecho: debido a que este código no itera sobre nada, incluso puede reemplazarlo con una función generadora que genera valores según sea necesario:
function getNextInterval() { // do whatever you like here return some number; } (function runIrregularly(runThisCode) { if (intervals.length > 0) { const timeout = getNextInterval(); ...
(Incluso podría volverse elegante usando una función de generador si lo desea)
Además, no tenemos que llamar a la función "tal como la declaramos", por supuesto, también podemos declararla y usarla por separado:
function runIrregularly(runThisCode) { if (intervals.length > 0) { ... } runThisCode(); } // and then at some later point in the code runIrregularly(doSomething);
Y finalmente, si necesita que esto funcione en ciclo, en lugar de ejecutarlo una vez, podemos combinar shift
con push
y copiar la lista de intervalos cuando comencemos:
function runIrregularly(runThisCode, intervals) { // get and remove the first element from the array const timeout = intervals.shift(); // then push it back on as the last element. intervals.push(timeout); setTimeout( () => runIrregularly(runThisCode, intervals), timeout * 1000 ); runThisCode(); } // and then later: runIrregularly(doSomething, intervals.slice());
Siendo necesario el interval.slice()
para que dejemos la matriz original prístina (de lo contrario, usar para otras funciones hará que no comiencen en su primer intervalo, sino "donde sea que estemos en el ciclo" según la cantidad de llamadas que se estén ejecutando) .
Cómo capturar y detener los tiempos de espera, lo dejo como ejercicio para el lector (hay muchas preguntas/publicaciones sobre eso en la web/SO para encontrar).
También puede lograr esto con async/await en combinación con setTimeout :
setTimeout(function() { }, 1000);
Ejecutaría la función después de que haya pasado 1 segundo.
Puede lograr su objetivo anidando llamadas setTimeout
(ver la respuesta de Mike) o con promesas (async/await):
function delayExecution(n) { return new Promise((resolve) => { setTimeout(resolve, n) }) } async function runIrregularly(fn, delays) { for (const delay of delays) { // suspend execution by `delay` amounts of seconds await delayExecution(delay * 1000) fn() } } runIrregularly(() => { console.log(Date.now(), "Hello") }, [1, 2, 3])