Poczekaj, aż flag = true


96

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”?


3
Użyj wzorca obietnica swoimi inicjalizacji - można znaleźć sporo bibliotek podoba jQuery.Deferred, Q, async, ...
Sirko

gdzie dokładnie go używać i jak?
ilay zeidman

1
Istnieje wiele samouczków opisujących obiecujące implementacje różnych bibliotek, np. jQuery.Deferred lub P . Przy okazji, twój podstawowy problem jest taki sam jak w tym pytaniu .
Sirko,

3
Dla kogoś, kto czyta to w 2018 roku, Obietnice są obsługiwane przez wszystkie przeglądarki oprócz opera mini i IE11.
Daniel Reina,

Głównym problemem jest to, że niemożliwe jest wykonanie naprawdę blokującego (uśpienia) czekania w jednowątkowych jsach podzielonych na zdarzenia. Możesz tylko utworzyć procedurę obsługi oczekiwania. zobacz więcej: stackoverflow.com/questions/41842147/…
SalientBrain

Odpowiedzi:


74

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ść flagnigdy 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 flagzmieni 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 natruezacznie 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();
    }
}

99

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ład

function 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.


1
Chociaż z jednej strony bardzo podoba mi się ta odpowiedź, ponieważ rzeczywiście jest to „czekaj” js, staje się nie tak użyteczna, jeśli chcesz zwrócić wartość. Jeśli nie zwrócisz wartości, nie jestem pewien, czy istnieje prawdziwy przypadek użycia wzorca?
Martin Meeser

Możesz oczywiście zwrócić obietnicę i zaimplementować funkcję w ten sposób. Zwykle wymagałoby to jednak biblioteki innej firmy, która implementuje obietnice lub wypełnienie polyfill, chyba że używasz ECMA-262. Najlepszym sposobem bez zwracania obietnicy jest użycie mechanizmu wywołania zwrotnego w celu zasygnalizowania dzwoniącemu, że wynik jest dostępny.
Kiran

W razie potrzeby możesz również przekazać parametry: stackoverflow.com/questions/1190642/ ...
SharpC

1
To świetna odpowiedź. Prawie się poddawałem po przekopaniu się przez wiele forów technicznych. Myślę, że to idealnie działało dla mnie, ponieważ zwracałem wartość. Działał również w przeglądarce Internet Explorer, dziękuję.
Joseph

19

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');
}

EventEmitterjest wbudowany w node. W przeglądarce musisz dodać go samodzielnie, na przykład za pomocą tego pakietu: https://www.npmjs.com/package/eventemitter3


17

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)

1
Jak ludzie to przegapili
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();
    }
}

Posługiwać się:

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

11

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
};


8

Nowoczesne rozwiązanie wykorzystujące Promise

myFunction() w pierwotnym pytaniu można zmodyfikować w następujący sposób

async function myFunction(number) {

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

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

    ...
    ... do something

}

gdzie until()jest ta funkcja narzędziowa

function 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


4

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=falsewłaściwość niestandardową dla każdego.

Następnie w funkcji rekurencyjnej ustaw każdy z nich done=truei kontynuuj używanie setTimeout. (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)
        }
}

1

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
}

1

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, thisnie jest moim obiektem, to jest Window. Skończyłem więc z kodem poniżej

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

1

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);


1

istnieje pakiet węzłów delaybardzo łatwy w użyciu

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

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

1

Jeśli możesz używać: async/awaitw 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.


1

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.


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

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");
});

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.