¿Cómo esperar hasta que una condición predicada se vuelva verdadera en JavaScript?

9 minutos de lectura

avatar de usuario de ilay zeidman
ilay zeidman

Tengo una función javascript como esta:

function myFunction(number) {

    var x=number;
    ...
    ... more initializations
    //here need to wait until flag==true
    while(flag==false)
    {}

    ...
    ... do something

}

El problema es que el javascript se atascó en el tiempo y atascó mi programa. entonces mi pregunta es ¿cómo puedo esperar en medio de la función hasta que el indicador sea verdadero sin “esperar ocupado”?

  • Use el patrón de promesa para sus inicializaciones; se puede encontrar en bastantes bibliotecas como jQuery.Deferred, Q, async

    – Sirko

    2 de marzo de 2014 a las 9:24

  • ¿dónde exactamente usarlo y cómo?

    – ilay zeidman

    2 de marzo de 2014 a las 9:24

  • Hay muchos tutoriales que describen las implementaciones prometedoras de las diversas bibliotecas, por ejemplo. jQuery.Diferido o q. Por cierto, su problema subyacente es el mismo que en esta pregunta.

    – Sirko

    2 de marzo de 2014 a las 9:30


  • Para alguien que lea esto en 2018, Promises es compatible con todos los navegadores además de Opera Mini e IE11.

    –Daniel Reina

    19 de febrero de 2018 a las 19:57

  • El problema principal es que es imposible hacer una espera de bloqueo (reposo) real en js de un solo subproceso basado en eventos. Solo puede crear un controlador de espera. ver más: stackoverflow.com/questions/41842147/…

    – Cerebro saliente

    1 oct 2019 a las 13:59


Avatar de usuario de Kiran
Kiran

Javascript tiene un solo subproceso, de ahí el comportamiento de bloqueo de página. Puede utilizar el enfoque diferido/promesa sugerido por otros. La forma más básica sería usar window.setTimeout. P.ej

function checkFlag() {
    if(flag === false) {
       window.setTimeout(checkFlag, 100); /* this checks the flag every 100 milliseconds*/
    } else {
      /* do something*/
    }
}
checkFlag();

Aquí hay un buen tutorial con más explicaciones: Tutorial

EDITAR

Como señalaron otros, la mejor manera sería reestructurar su código para usar devoluciones de llamada. Sin embargo, esta respuesta debería darle una idea de cómo puede ‘simular’ un comportamiento asíncrono con window.setTimeout.

  • Si bien, por un lado, me gusta mucho esta respuesta porque, de hecho, es una ‘espera’ de js, no se vuelve tan útil si desea devolver un valor. Si no devuelve un valor, no estoy tan seguro de que haya un caso de uso real para el patrón.

    – Martín Meeser

    18/04/2016 a las 19:54

  • Por supuesto, puede devolver una promesa e implementar la función de esa manera. Sin embargo, esto generalmente requeriría una biblioteca de terceros que implemente promesas o polyfill, a menos que esté utilizando ECMA-262. Sin devolver una promesa, la mejor manera es utilizar un mecanismo de devolución de llamada para señalar a la persona que llama que hay un resultado disponible.

    – Kiran

    4 de noviembre de 2016 a las 4:21

  • También puede pasar parámetros si es necesario: stackoverflow.com/questions/1190642/…

    – SharpC

    20 sep 2017 a las 8:00

  • Esta es una gran respuesta. Casi me rendía después de buscar en muchos foros de tecnología. Creo que funcionó perfectamente para mí porque estaba devolviendo un valor. También funcionó en Internet Explorer. Muchas gracias.

    – Joseph

    2 oct 2019 a las 20:02

  • Si por alguna razón necesitamos enviar parámetros a la función checkFlag, entonces tenemos que usar la función anónima/flecha como, window.setTimeout( () => { checkFlag(params); }, 100);

    – Said

    16 de febrero de 2021 a las 14:00


avatar de usuario de jfriend00
amigo00

Debido a que javascript en un navegador tiene un solo subproceso (excepto para los trabajadores web que no están involucrados aquí) y un subproceso de ejecución de javascript se ejecuta hasta completarse antes de que se pueda ejecutar otro, su declaración:

while(flag==false) {}

simplemente se ejecutará para siempre (o hasta que el navegador se queje de un bucle de javascript que no responde), la página parecerá estar bloqueada y ningún otro javascript tendrá la oportunidad de ejecutarse, por lo que el valor de la bandera nunca se puede cambiar.

Para un poco más de explicación, Javascript es un lenguaje dirigido por eventos. Eso significa que ejecuta una parte de Javascript hasta que devuelve el control al intérprete. Luego, solo cuando regresa al intérprete, Javascript obtiene el siguiente evento de la cola de eventos y lo ejecuta.

Todas las cosas, como temporizadores y eventos de red, se ejecutan en la cola de eventos. Entonces, cuando se dispara un temporizador o llega una solicitud de red, nunca “interrumpe” el Javascript que se está ejecutando actualmente. En cambio, un evento se coloca en la cola de eventos de Javascript y luego, cuando finaliza el Javascript que se está ejecutando actualmente, el siguiente evento se extrae de la cola de eventos y le toca ejecutarse.

Entonces, cuando haces un bucle infinito como while(flag==false) {}el Javascript que se ejecuta actualmente nunca finaliza y, por lo tanto, el siguiente evento nunca se extrae de la cola de eventos y, por lo tanto, el valor de flag nunca se cambia. La clave aquí es que Javascript no está controlado por interrupciones. Cuando se dispara un temporizador, no interrumpe el Javascript que se está ejecutando actualmente, ejecuta otro Javascript y luego deja que el Javascript que se está ejecutando actualmente continúe. Simplemente se coloca en la cola de eventos esperando hasta que el Javascript que se está ejecutando actualmente termine para obtener su turno para ejecutarse.


Lo que debe hacer es repensar cómo funciona su código y encontrar una forma diferente de activar cualquier código que desee ejecutar cuando el flag cambios de valor. Javascript está diseñado como un lenguaje basado en eventos. Entonces, lo que debe hacer es averiguar en qué eventos puede registrar un interés para que pueda escuchar el evento que podría causar que la bandera cambie y pueda examinar la bandera en ese evento o puede desencadenar su propio evento desde cualquier código podría cambiar la bandera o puede implementar una función de devolución de llamada que cualquier código que cambie esa bandera puede llamar a su devolución de llamada cada vez que la pieza de código responsable de cambiar el valor de la bandera cambiaría su valor a truesolo llama a la función de devolución de llamada y, por lo tanto, a su código que desea ejecutar cuando la bandera se establece en true llegará a funcionar en el momento adecuado. Esto es mucho, mucho más eficiente que tratar de usar algún tipo de temporizador para verificar constantemente el valor de la bandera.

function codeThatMightChangeFlag(callback) {
    // do a bunch of stuff
    if (condition happens to change flag value) {
        // call the callback to notify other code
        callback();
    }
}

Avatar de usuario de Yaroslav Dobzhanskij
Yaroslav Dobzhanskij

Solución usando Promesaasíncrono\esperar y EventEmitter lo que permite reaccionar inmediatamente al cambio de bandera sin ningún tipo de bucles

const EventEmitter = require('events');

const bus = new EventEmitter();
let lock = false;

async function lockable() {
    if (lock) await new Promise(resolve => bus.once('unlocked', resolve));
    ....
    lock = true;
    ...some logic....
    lock = false;
    bus.emit('unlocked');
}

EventEmitter está incorporado en el nodo. En el navegador, deberá incluirlo usted mismo, por ejemplo, utilizando este paquete: https://www.npmjs.com/package/eventemitter3

  • Un ejemplo de cómo se puede usar este código: (1) Al principio, lock Es falso. (2) Algunas llamadas de código lockable. Ese código evalúa if (lock) a falso, por lo que continúa: establece lock a verdadero, luego pasa a ejecutar algo de lógica. Mientras tanto: (3) Algunas otras llamadas de código lockable. Ese código, sin embargo, evalúa if (lock) a verdadero, por lo que espera en la promesa, hasta que un unlocked se emitirá el evento. (4) Vuelta al primer código de llamada: Finaliza su lógica, establece lock a falso, y emite un unlocked evento. (5) El otro código ahora puede continuar su ejecución.

    – OfirD

    8 de junio de 2021 a las 18:23

ES6 con Async / Await,

let meaningOfLife = false;
async function waitForMeaningOfLife(){
   while (true){
        if (meaningOfLife) { console.log(42); return };
        await null; // prevents app from hanging
   }
}
waitForMeaningOfLife();
setTimeout(()=>meaningOfLife=true,420)

Solución moderna usando Promise

myFunction() en la pregunta original se puede modificar de la siguiente manera

async function myFunction(number) {

    var x=number;
    ...
    ... more initializations

    await until(_ => flag == true);

    ...
    ... do something

}

dónde until() es esta función de utilidad

function until(conditionFunction) {

  const poll = resolve => {
    if(conditionFunction()) resolve();
    else setTimeout(_ => poll(resolve), 400);
  }

  return new Promise(poll);
}

Algunas referencias a las funciones async/await y arrow se encuentran en una publicación similar: https://stackoverflow.com/a/52652681/209794

  • Esta es la única solución que he visto que le permite “esperar” fácilmente una señal, pero no “regresar” de myFunction hasta que se cumpla la condición.

    – Roddy

    25 de febrero de 2021 a las 10:06

  • He probado las dos mejores respuestas y esta ha funcionado mejor con diferencia. No solo es el más fácil y limpio, sino que tiene el sentido más lógico y sintáctico. ¡Gracias!

    – jack.py

    15 de agosto de 2021 a las 14:25

  • Preferiría el operador condicional: conditionFunction() ? resolver() : setTimeout(() => encuesta(resolver), 400)

    – Julian

    25 de agosto de 2021 a las 11:44


  • Parece una buena solución, pero podría arreglarse un poco: he publicado una respuesta para ordenar.

    – Steve Cámaras

    23 de mayo a las 15:21

Avatar de usuario de Maciej Dudziński
Maciej Dudziński

function waitFor(condition, callback) {
    if(!condition()) {
        console.log('waiting');
        window.setTimeout(waitFor.bind(null, condition, callback), 100); /* this checks the flag every 100 milliseconds*/
    } else {
        console.log('done');
        callback();
    }
}

Usar:

waitFor(() => window.waitForMe, () => console.log('got you'))

  • Esta es la única solución que he visto que le permite “esperar” fácilmente una señal, pero no “regresar” de myFunction hasta que se cumpla la condición.

    – Roddy

    25 de febrero de 2021 a las 10:06

  • He probado las dos mejores respuestas y esta ha funcionado mejor con diferencia. No solo es el más fácil y limpio, sino que tiene el sentido más lógico y sintáctico. ¡Gracias!

    – jack.py

    15 de agosto de 2021 a las 14:25

  • Preferiría el operador condicional: conditionFunction() ? resolver() : setTimeout(() => encuesta(resolver), 400)

    – Julian

    25 de agosto de 2021 a las 11:44


  • Parece una buena solución, pero podría arreglarse un poco: he publicado una respuesta para ordenar.

    – Steve Cámaras

    23 de mayo a las 15:21

Resolví este problema implementando el método a continuación.

const waitUntil = (condition) => {
    return new Promise((resolve) => {
        let interval = setInterval(() => {
            if (!condition()) {
                return
            }

            clearInterval(interval)
            resolve()
        }, 100)
    })
}

Ahora, cada vez que desee esperar hasta que se cumpla una determinada condición, puede llamarlo así.

await waitUntil(() => /* your condition */)

  • Creo que debería ser esperar no esperar en la invocación.

    – mega_creamery

    15 de diciembre de 2020 a las 14:08

  • @mega_creamery gracias por la corrección. Arreglé el error tipográfico 🙂

    – tdxius

    18 de diciembre de 2020 a las 20:38

  • En mi opinión, esta fue la solución más limpia y simple.

    – Decano

    13 de julio de 2021 a las 15:12

¿Ha sido útil esta solución?