Mam taką funkcję javascript:
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{}
...
... do something
}
Problem polega na tym, że javascript utknął w czasie i zablokował mój program. więc moje pytanie brzmi: jak mogę czekać w środku funkcji, aż flaga stanie się prawdziwa, bez „zajętości oczekiwania”?
javascript
synchronization
ilay zeidman
źródło
źródło
jQuery.Deferred
,Q
,async
, ...Odpowiedzi:
Ponieważ javascript w przeglądarce jest jednowątkowy (z wyjątkiem webworkerów, którzy nie są tutaj zaangażowani), a jeden wątek wykonywania javascript działa do końca, zanim inny będzie mógł zostać uruchomiony, Twoja instrukcja:
while(flag==false) {}
będzie po prostu działać w nieskończoność (lub dopóki przeglądarka nie będzie narzekać na nieodpowiadającą pętlę javascript), strona będzie wyglądać na zawieszoną i żaden inny javascript nigdy nie będzie miał szansy na uruchomienie, więc wartość flagi nigdy nie może zostać zmieniona.
Aby uzyskać więcej wyjaśnień, JavaScript jest językiem sterowanym zdarzeniami . Oznacza to, że uruchamia fragment JavaScript, dopóki nie zwróci kontroli z powrotem do interpretera. Następnie, dopiero po powrocie do interpretera, Javascript pobiera następne zdarzenie z kolejki zdarzeń i uruchamia je.
Wszystkie rzeczy, takie jak liczniki czasu i zdarzenia sieciowe, przechodzą przez kolejkę zdarzeń. Tak więc, gdy uruchamia się licznik czasu lub przychodzi żądanie sieciowe, nigdy nie „przerywa” aktualnie działającego JavaScript. Zamiast tego zdarzenie jest umieszczane w kolejce zdarzeń Javascript, a następnie, po zakończeniu aktualnie działającego kodu JavaScript, następne zdarzenie jest pobierane z kolejki zdarzeń i wykonuje swoją kolej.
Tak więc, gdy wykonujesz nieskończoną pętlę, taką jak
while(flag==false) {}
aktualnie działający JavaScript, nigdy się nie kończy, a zatem następne zdarzenie nigdy nie jest pobierane z kolejki zdarzeń, a zatem wartośćflag
nigdy nie jest zmieniana. Kluczowe jest tutaj to, że Javascript nie jest sterowany przerwaniami . Uruchomiony licznik czasu nie przerywa aktualnie uruchomionego Javascript, nie uruchamia innego Javascript, a następnie pozwala kontynuować aktualnie działający Javascript. Po prostu zostaje umieszczony w kolejce zdarzeń, czekając, aż aktualnie działający Javascript zakończy swoją kolej.To, co musisz zrobić, to przemyśleć, jak działa twój kod i znaleźć inny sposób wyzwalania dowolnego kodu, który chcesz uruchomić, gdy
flag
zmieni się wartość. JavaScript jest zaprojektowany jako język sterowany zdarzeniami. Więc to, co musisz zrobić, to dowiedzieć się, jakie zdarzenia możesz zarejestrować, abyś mógł albo nasłuchiwać zdarzenia, które może spowodować zmianę flagi, i możesz sprawdzić flagę tego zdarzenia lub wyzwolić własne zdarzenie z jakikolwiek kod może zmienić flagę lub możesz zaimplementować funkcję zwrotną, która niezależnie od tego, czy kod zmieni tę flagę, może wywołać wywołanie zwrotne za każdym razem, gdy fragment kodu odpowiedzialny za zmianę wartości flagi zmieni jej wartośćtrue
, po prostu wywoła funkcję zwrotną, a tym samym twój kod który chce działać, gdy flaga zostanie ustawiona natrue
zacznie działać we właściwym czasie. Jest to dużo, dużo bardziej wydajne niż próba użycia jakiegoś timera do ciągłego sprawdzania wartości flagi.function codeThatMightChangeFlag(callback) { // do a bunch of stuff if (condition happens to change flag value) { // call the callback to notify other code callback(); } }
źródło
JavaScript jest jednowątkowy, stąd zachowanie blokujące strony. Możesz użyć podejścia odroczonego / obiecanego sugerowanego przez innych, ale najbardziej podstawowym sposobem byłoby użycie
window.setTimeout
. Na przykładfunction checkFlag() { if(flag == false) { window.setTimeout(checkFlag, 100); /* this checks the flag every 100 milliseconds*/ } else { /* do something*/ } } checkFlag();
Oto dobry samouczek z dodatkowymi wyjaśnieniami: Samouczek
EDYTOWAĆ
Jak wskazywali inni, najlepszym sposobem byłoby przeprojektowanie kodu tak, aby używał wywołań zwrotnych. Jednak ta odpowiedź powinna dać ci wyobrażenie, jak możesz „symulować” zachowanie asynchroniczne za pomocą
window.setTimeout
.źródło
Rozwiązanie wykorzystujące Promise , async \ await i EventEmitter, które umożliwia natychmiastową reakcję na zmianę flagi bez żadnych pętli
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
jest wbudowany w node. W przeglądarce musisz dodać go samodzielnie, na przykład za pomocą tego pakietu: https://www.npmjs.com/package/eventemitter3źródło
ES6 z 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)
źródło
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(); } }
Posługiwać się:
waitFor(() => window.waitForMe, () => console.log('got you'))
źródło
Z Ecma Script 2017 możesz użyć async-await i będąc razem, aby to zrobić I chociaż nie ulegnie awarii ani nie zablokuje programu nawet zmienna nigdy nie będzie prawdziwa
//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 };
źródło
Nowoczesne rozwiązanie wykorzystujące Promise
myFunction()
w pierwotnym pytaniu można zmodyfikować w następujący sposóbasync function myFunction(number) { var x=number; ... ... more initializations await until(_ => flag == true); ... ... do something }
gdzie
until()
jest ta funkcja narzędziowafunction until(conditionFunction) { const poll = resolve => { if(conditionFunction()) resolve(); else setTimeout(_ => poll(resolve), 400); } return new Promise(poll); }
Niektóre odniesienia do funkcji async / await i strzałek znajdują się w podobnym poście: https://stackoverflow.com/a/52652681/209794
źródło
Do iteracji po obiektach ($ .each) i wykonywania długotrwałej operacji (zawierającej zagnieżdżone wywołania synchronizacji ajax) na każdym obiekcie:
Najpierw ustawiłem
done=false
właściwość niestandardową dla każdego.Następnie w funkcji rekurencyjnej ustaw każdy z nich
done=true
i kontynuuj używaniesetTimeout
. (Jest to operacja mająca na celu zatrzymanie wszystkich innych interfejsów użytkownika, wyświetlenie paska postępu i zablokowanie wszystkich innych zastosowań, więc wybaczyłem sobie wywołania synchronizacji).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) } }
źródło
Podobnie jak w przypadku odpowiedzi Lightbearda, stosuję następujące podejście
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 }
źródło
Próbowałem zastosować podejście @Kiran w następujący sposób:
checkFlag: function() { var currentObject = this; if(flag == false) { setTimeout(currentObject.checkFlag, 100); } else { /* do something*/ } }
(framework, którego używam, zmusza mnie do definiowania funkcji w ten sposób). Ale bez powodzenia, ponieważ gdy wykonanie pojawia się w funkcji checkFlag po raz drugi,
this
nie jest moim obiektem, to jestWindow
. Skończyłem więc z kodem poniżejcheckFlag: function() { var worker = setInterval (function(){ if(flag == true){ /* do something*/ clearInterval (worker); } },100); }
źródło
używając nieblokującego javascript z EventTarget API
W moim przykładzie muszę poczekać na oddzwonienie, zanim z niego skorzystam. Nie mam pojęcia, kiedy to wywołanie zwrotne jest ustawione. Może to nastąpić przed lub po tym, jak muszę to wykonać. Mogę to wywołać kilka razy (wszystko asynchroniczne)
// 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);
źródło
istnieje pakiet węzłów
delay
bardzo łatwy w użyciuconst delay = require('delay'); (async () => { bar(); await delay(100); // Executed 100 milliseconds later baz(); })();
źródło
Jeśli możesz używać:
async/await
w swoim kodzie, możesz spróbować tego: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();
Demo tutaj: https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts
Na konsoli, tylko kopiuj / wklej:
goahead = true
.źródło
Przyjąłem tutaj podejście podobne do rozwiązań wywołania zwrotnego, ale starałem się, aby było trochę bardziej ogólne. Chodzi o to, że dodajesz funkcje, które musisz wykonać, gdy coś zmieni się w kolejce. Kiedy coś się wydarzy, zapętlasz kolejkę, wywołujesz funkcje i opróżniasz kolejkę.
Dodaj funkcję do kolejki:
let _queue = []; const _addToQueue = (funcToQ) => { _queue.push(funcToQ); }
Wykonaj i opróżnij kolejkę:
const _runQueue = () => { if (!_queue || !_queue.length) { return; } _queue.forEach(queuedFunc => { queuedFunc(); }); _queue = []; }
A kiedy wywołasz _addToQueue, będziesz chciał zawinąć wywołanie zwrotne:
_addToQueue(() => methodYouWantToCallLater(<pass any args here like you normally would>));
Po spełnieniu warunku zadzwoń
_runQueue()
Było to dla mnie przydatne, ponieważ miałem kilka rzeczy, które musiały czekać w tym samym stanie. I oddziela wykrywanie warunku od tego, co musi zostać wykonane, gdy ten warunek zostanie osiągnięty.
źródło
//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();
źródło
W moim przykładzie rejestruję nową wartość licznika co sekundę:
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"); });
źródło