Espere hasta que flag = true


96

Tengo una función de 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 está bloqueado en el while y atascado mi programa. entonces mi pregunta es ¿cómo puedo esperar en medio de la función hasta que el indicador sea verdadero sin "ocupado-espera"?


3
Utilice el patrón promesa para sus inicializaciones - se pueden encontrar en bastantes bibliotecas como jQuery.Deferred, Q, async, ...
Sirko

¿Dónde exactamente usarlo y cómo?
ilay zeidman

1
Hay muchos tutoriales que describen las implementaciones de promesas de las diversas bibliotecas, por ejemplo jQuery.Deferred o Q . Por cierto, su problema subyacente es el mismo que en esta pregunta .
Sirko

3
Para alguien que lea esto en 2018, Promises es compatible con todos los navegadores además de Opera mini e IE11.
Daniel Reina

El principal problema es que es imposible realizar una espera de bloqueo (suspensión) real en js de un solo subproceso dividido en eventos. Solo puede crear un controlador de espera. ver más: stackoverflow.com/questions/41842147/…
SalientBrain

Respuestas:


74

Debido a que javascript en un navegador es de un solo hilo (excepto para los webworkers que no están involucrados aquí) y un hilo de ejecución de javascript se ejecuta hasta su finalización antes de que otro pueda ejecutarse, su declaración:

while(flag==false) {}

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

Para una explicación más detallada, Javascript es un lenguaje impulsado 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 los temporizadores y los eventos de la red, se ejecutan en la cola de eventos. Por lo tanto, cuando se activa 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 tiene su turno para ejecutarse.

Entonces, cuando haces un ciclo infinito como while(flag==false) {}, el Javascript que se está ejecutando actualmente nunca termina y, por lo tanto, el siguiente evento nunca se extrae de la cola de eventos y, por lo tanto, el valor de flagnunca cambia. La clave aquí es que Javascript no está controlado por interrupciones . Cuando se activa un temporizador, no interrumpe el Javascript que se está ejecutando actualmente, ejecuta algún 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 esté listo para que se ejecute su turno.


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 flagcambie el valor. Javascript está diseñado como un lenguaje impulsado por 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 activar su propio evento desde cualquier código podría cambiar la bandera o puede implementar una función de devolución de llamada en la que cualquier código que cambie esa bandera puede llamar a su devolución de llamada siempre que el fragmento de código responsable de cambiar el valor de la bandera cambie su valor a true, solo llama a la función de devolución de llamada y, por lo tanto, su código que quiere correr cuando la bandera se pone entruellegará a ejecutarse en el momento adecuado. Esto es mucho, mucho más eficiente que intentar 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();
    }
}

99

Javascript es de un solo hilo, de ahí el comportamiento de bloqueo de la página. Puede utilizar el enfoque diferido / prometido sugerido por otros, pero la forma más básica sería utilizar 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 asincrónico con window.setTimeout.


1
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 exista un caso de uso real para el patrón.
Martin Meeser

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é usando ECMA-262. Sin devolver una promesa, la mejor manera es utilizar un mecanismo de devolución de llamada para indicar a la persona que llama que hay un resultado disponible.
Kiran

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

1
Esta es una gran respuesta. Casi me estaba rindiendo 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

19

Solución que utiliza Promise , async \ await y EventEmitter 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');
}

EventEmitterestá incorporado en el nodo. En el navegador, deberá incluirlo por su cuenta, por ejemplo, usando este paquete: https://www.npmjs.com/package/eventemitter3


17

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)

1
¿Cómo se perdió la gente esto?
Aviad

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

Utilizar:

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

11

Con Ecma Script 2017 puede usar async-await y mientras están juntos para hacer eso Y mientras no se bloquee ni bloquee el programa, incluso la variable nunca será verdadera

//First define some delay function which is called from async function
function __delay__(timer) {
    return new Promise(resolve => {
        timer = timer || 2000;
        setTimeout(function () {
            resolve();
        }, timer);
    });
};

//Then Declare Some Variable Global or In Scope
//Depends on you
var flag = false;

//And define what ever you want with async fuction
async function some() {
    while (!flag)
        await __delay__(1000);

    //...code here because when Variable = true this function will
};


8

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()está 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


4

Para iterar sobre ($ .each) objetos y ejecutar una operación de ejecución larga (que contiene llamadas de sincronización ajax anidadas) en cada objeto:

Primero establecí una done=falsepropiedad personalizada en cada uno.

Luego, en una función recursiva, configure cada uno done=truey continúe usándolo setTimeout. (Es una operación destinada a detener todas las demás IU, mostrar una barra de progreso y bloquear todos los demás usos, así que me perdoné por las llamadas de sincronización).

function start()
{
    GlobalProducts = getproductsfromsomewhere();
    $.each(GlobalProducts, function(index, product) {
         product["done"] = false;
    });

    DoProducts();
}
function DoProducts()
{
    var doneProducts = Enumerable.From(GlobalProducts).Where("$.done == true").ToArray(); //linqjs

    //update progress bar here

    var nextProduct = Enumerable.From(GlobalProducts).Where("$.done == false").First();

        if (nextProduct) {
            nextProduct.done = true;
            Me.UploadProduct(nextProduct.id); //does the long-running work

            setTimeout(Me.UpdateProducts, 500)
        }
}

1

Similar a la respuesta de Lightbeard, uso el siguiente enfoque

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function until(fn) {
    while (!fn()) {
        await sleep(0)
    }
}

async function myFunction(number) {
    let x = number
    ...
    ... more initialization

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

    ...
    ... do something
}

1

Intenté usar el enfoque @Kiran como sigue:

checkFlag: function() {
  var currentObject = this; 
  if(flag == false) {
      setTimeout(currentObject.checkFlag, 100); 
   } else {
     /* do something*/
   }
}

(el marco que estoy usando me obliga a definir funciones de esta manera). Pero sin éxito porque cuando la ejecución entra dentro de la función checkFlag por segunda vez, thisno es mi objeto Window. Entonces, terminé con el código a continuación

checkFlag: function() {
    var worker = setInterval (function(){
         if(flag == true){             
             /* do something*/
              clearInterval (worker);
         } 
    },100);
 }

1

usando javascript sin bloqueo con EventTarget API

En mi ejemplo, necesito esperar una devolución de llamada antes de usarlo. No tengo idea de cuándo se establece esta devolución de llamada. Puede ser antes o después de que necesite ejecutarlo. Y puedo necesitar llamarlo varias veces (todo asincrónico)

// bus to pass event
const bus = new EventTarget();

// it's magic
const waitForCallback = new Promise((resolve, reject) => {
    bus.addEventListener("initialized", (event) => {
        resolve(event.detail);
    });
});



// LET'S TEST IT !


// launch before callback has been set
waitForCallback.then((callback) => {
    console.log(callback("world"));
});


// async init
setTimeout(() => {
    const callback = (param) => { return `hello ${param.toString()}`; }
    bus.dispatchEvent(new CustomEvent("initialized", {detail: callback}));
}, 500);


// launch after callback has been set
setTimeout(() => {
    waitForCallback.then((callback) => {
        console.log(callback("my little pony"));
    });
}, 1000);


1

hay un paquete de nodos delaymuy fácil de usar

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

    // Executed 100 milliseconds later
    baz();
})();

1

Si tiene permiso para usar: async/awaiten su código, puede probar este:

const waitFor = async (condFunc: () => boolean) => {
  return new Promise((resolve) => {
    if (condFunc()) {
      resolve();
    }
    else {
      setTimeout(async () => {
        await waitFor(condFunc);
        resolve();
      }, 100);
    }
  });
};

const myFunc = async () => {
  await waitFor(() => (window as any).goahead === true);
  console.log('hello world');
};

myFunc();

Demostración aquí: https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts

En la consola, sólo tienes que copiar / pegar: goahead = true.


1

Adopté un enfoque similar a las soluciones de devolución de llamada aquí, pero traté de hacerlo un poco más genérico. La idea es agregar funciones que necesita ejecutar después de que algo cambia en una cola. Cuando sucede, recorre la cola, llama a las funciones y vacía la cola.

Agregar función a la cola:

let _queue = [];

const _addToQueue = (funcToQ) => {
    _queue.push(funcToQ);
}

Ejecute y vacíe la cola:

const _runQueue = () => {
    if (!_queue || !_queue.length) {
        return;
    }

    _queue.forEach(queuedFunc => {
        queuedFunc();
    });

    _queue = [];
}

Y cuando invoques _addToQueue, querrás envolver la devolución de llamada:

_addToQueue(() => methodYouWantToCallLater(<pass any args here like you normally would>));

Cuando haya cumplido la condición, llame _runQueue()

Esto fue útil para mí porque tenía varias cosas que debían esperar en la misma condición. Y desacopla la detección de la condición de lo que sea necesario ejecutar cuando se alcanza esa condición.


0

//function a(callback){
setTimeout(function() {
  console.log('Hi I am order 1');
}, 3000);
 // callback();
//}

//function b(callback){
setTimeout(function() {
  console.log('Hi I am order 2');
}, 2000);
//   callback();
//}



//function c(callback){
setTimeout(function() {
  console.log('Hi I am order 3');
}, 1000);
//   callback();

//}

 
/*function d(callback){
  a(function(){
    b(function(){
      
      c(callback);
      
    });
    
  });
  
  
}
d();*/


async function funa(){
  
  var pr1=new Promise((res,rej)=>{
    
   setTimeout(()=>res("Hi4 I am order 1"),3000)
        
  })
  
  
   var pr2=new Promise((res,rej)=>{
    
   setTimeout(()=>res("Hi4 I am order 2"),2000)
        
  })
   
    var pr3=new Promise((res,rej)=>{
    
   setTimeout(()=>res("Hi4 I am order 3"),1000)
        
  })

              
  var res1 = await pr1;
  var res2 = await pr2;
  var res3 = await pr3;
  console.log(res1,res2,res3);
  console.log(res1);
   console.log(res2);
   console.log(res3);

}   
    funa();
              


async function f1(){
  
  await new Promise(r=>setTimeout(r,3000))
    .then(()=>console.log('Hi3 I am order 1'))
    return 1;                        

}

async function f2(){
  
  await new Promise(r=>setTimeout(r,2000))
    .then(()=>console.log('Hi3 I am order 2'))
         return 2;                   

}

async function f3(){
  
  await new Promise(r=>setTimeout(r,1000))
    .then(()=>console.log('Hi3 I am order 3'))
        return 3;                    

}

async function finaloutput2(arr){
  
  return await Promise.all([f3(),f2(),f1()]);
}

//f1().then(f2().then(f3()));
//f3().then(f2().then(f1()));
  
//finaloutput2();

//var pr1=new Promise(f3)







async function f(){
  console.log("makesure");
  var pr=new Promise((res,rej)=>{
  setTimeout(function() {
  console.log('Hi2 I am order 1');
}, 3000);
  });
    
  
  var result=await pr;
  console.log(result);
}

 // f(); 

async function g(){
  console.log("makesure");
  var pr=new Promise((res,rej)=>{
  setTimeout(function() {
  console.log('Hi2 I am order 2');
}, 2000);
  });
    
  
  var result=await pr;
  console.log(result);
}
  
// g(); 

async function h(){
  console.log("makesure");
  var pr=new Promise((res,rej)=>{
  setTimeout(function() {
  console.log('Hi2 I am order 3');
}, 1000);
  });
    
  
  var result=await pr;
  console.log(result);
}

async function finaloutput(arr){
  
  return await Promise.all([f(),g(),h()]);
}
  
//finaloutput();

 //h(); 
  
  
  
  
  
  


0

En mi ejemplo, registro un nuevo valor de contador cada segundo:

var promises_arr = [];
var new_cntr_val = 0;

// fill array with promises
for (let seconds = 1; seconds < 10; seconds++) {
    new_cntr_val = new_cntr_val + 5;    // count to 50
    promises_arr.push(new Promise(function (resolve, reject) {
        // create two timeouts: one to work and one to resolve the promise
        setTimeout(function(cntr) {
            console.log(cntr);
        }, seconds * 1000, new_cntr_val);    // feed setTimeout the counter parameter
        setTimeout(resolve, seconds * 1000);
    }));
}

// wait for promises to finish
Promise.all(promises_arr).then(function (values) {
    console.log("all promises have returned");
});

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.