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"?
javascript
synchronization
ilay zeidman
fuente
fuente
jQuery.Deferred
,Q
,async
, ...Respuestas:
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 deflag
nunca 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
flag
cambie 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 atrue
, 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 entrue
llegará 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(); } }
fuente
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.ejfunction 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
.fuente
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'); }
EventEmitter
está incorporado en el nodo. En el navegador, deberá incluirlo por su cuenta, por ejemplo, usando este paquete: https://www.npmjs.com/package/eventemitter3fuente
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)
fuente
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'))
fuente
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 };
fuente
Solución moderna usando Promise
myFunction()
en la pregunta original se puede modificar de la siguiente maneraasync 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
fuente
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=false
propiedad personalizada en cada uno.Luego, en una función recursiva, configure cada uno
done=true
y continúe usándolosetTimeout
. (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) } }
fuente
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 }
fuente
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,
this
no es mi objetoWindow
. Entonces, terminé con el código a continuacióncheckFlag: function() { var worker = setInterval (function(){ if(flag == true){ /* do something*/ clearInterval (worker); } },100); }
fuente
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);
fuente
hay un paquete de nodos
delay
muy fácil de usarconst delay = require('delay'); (async () => { bar(); await delay(100); // Executed 100 milliseconds later baz(); })();
fuente
Si tiene permiso para usar:
async/await
en 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
.fuente
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.
fuente
//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();
fuente
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"); });
fuente