Warten Sie, bis flag = true ist


89

Ich habe Javascript-Funktion wie folgt:

function myFunction(number) {

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

    ...
    ... do something

}

Das Problem ist, dass das Javascript in der Zeit steckt und mein Programm steckt. Meine Frage ist also, wie ich in der Mitte der Funktion warten kann, bis das Flag wahr ist, ohne "beschäftigt zu warten".


3
Verwenden Sie das Versprechen Muster für Ihre Initialisierungen - kann in ganz bestimmten Bibliotheken wie gefunden werden jQuery.Deferred, Q, async, ...
Sirko

wo genau und wie?
Ilay Zeidman

1
Es gibt viele Tutorials, die die Versprechen-Implementierungen der verschiedenen Bibliotheken beschreiben, z. jQuery.Deferred oder Q . Übrigens ist Ihr zugrunde liegendes Problem das gleiche wie in dieser Frage .
Sirko

3
Für jemanden, der dies 2018 liest, werden Versprechen von allen Browsern außer Opera Mini und IE11 unterstützt.
Daniel Reina

Das Hauptproblem besteht darin, dass es unmöglich ist, das Warten in ereignisgesteuerten Single-Threaded-Js wirklich zu blockieren (Schlaf). Sie können nur einen Wartehandler erstellen. Weitere Informationen
SalientBrain

Antworten:


68

Da Javascript in einem Browser ein einzelner Thread ist (mit Ausnahme von Webworkern, die hier nicht beteiligt sind) und ein Thread der Javascript-Ausführung vollständig ausgeführt wird, bevor ein anderer ausgeführt werden kann, lautet Ihre Aussage:

while(flag==false) {}

wird einfach für immer ausgeführt (oder bis sich der Browser über eine nicht reagierende Javascript-Schleife beschwert), die Seite scheint hängen zu bleiben und kein anderes Javascript kann jemals ausgeführt werden, sodass der Wert des Flags niemals geändert werden kann.

Für ein wenig mehr Erklärung ist Javascript eine ereignisgesteuerte Sprache . Das bedeutet, dass ein Teil von Javascript ausgeführt wird, bis die Kontrolle an den Interpreter zurückgegeben wird. Erst wenn es zum Interpreter zurückkehrt, ruft Javascript das nächste Ereignis aus der Ereigniswarteschlange ab und führt es aus.

Alle Dinge wie Timer und Netzwerkereignisse werden in der Ereigniswarteschlange ausgeführt. Wenn also ein Timer ausgelöst wird oder eine Netzwerkanforderung eintrifft, "unterbricht" er niemals das aktuell ausgeführte Javascript. Stattdessen wird ein Ereignis in die Javascript-Ereigniswarteschlange gestellt. Wenn das aktuell ausgeführte Javascript beendet ist, wird das nächste Ereignis aus der Ereigniswarteschlange gezogen und kann ausgeführt werden.

Wenn Sie also eine Endlosschleife wie z. B. while(flag==false) {}ausführen, wird das aktuell ausgeführte Javascript nie beendet, und daher wird das nächste Ereignis nie aus der Ereigniswarteschlange gezogen, und daher wird der Wert von flagnie geändert. Der Schlüssel hier ist, dass Javascript nicht Interrupt-gesteuert ist . Wenn ein Timer ausgelöst wird, wird das aktuell ausgeführte Javascript nicht unterbrochen, es wird kein anderes Javascript ausgeführt und das aktuell ausgeführte Javascript wird fortgesetzt. Es wird nur in die Ereigniswarteschlange gestellt und wartet, bis das aktuell ausgeführte Javascript fertig ist, damit es an die Reihe kommt.


Was Sie tun müssen, ist zu überdenken, wie Ihr Code funktioniert, und einen anderen Weg zu finden, um den Code auszulösen, den Sie ausführen möchten, wenn der Codeflag Wert ändert. Javascript ist als ereignisgesteuerte Sprache konzipiert. Sie müssen also herausfinden, an welchen Ereignissen Sie ein Interesse anmelden können, damit Sie entweder auf das Ereignis warten können, durch das sich das Flag möglicherweise ändert, und das Flag für dieses Ereignis untersuchen oder Ihr eigenes Ereignis auslösen können Unabhängig davon, welcher Code das Flag ändert oder Sie eine Rückruffunktion implementieren können, kann jeder Code, der dieses Flag ändert, Ihren Rückruf aufrufen, wenn der Code, der für die Änderung des Flag-Werts verantwortlich ist, seinen Wert in ändert. Er trueruft lediglich die Rückruffunktion und damit Ihren Code auf das will laufen, wenn das Flag gesetzt wirdtruewird zur richtigen Zeit laufen. Dies ist viel, viel effizienter als der Versuch, den Flag-Wert ständig mit einem Timer zu überprüfen.

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

95

Javascript ist Single-Threaded, daher das Seitenblockierungsverhalten. Sie können den von anderen vorgeschlagenen Ansatz des verzögerten Versprechens verwenden, aber der grundlegendste Weg wäre die Verwendung window.setTimeout. Z.B

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

Hier ist ein gutes Tutorial mit weiteren Erklärungen: Tutorial

BEARBEITEN

Wie andere betonten, besteht der beste Weg darin, Ihren Code neu zu strukturieren, um Rückrufe zu verwenden. Diese Antwort sollte Ihnen jedoch eine Vorstellung davon geben, wie Sie ein asynchrones Verhalten mit "simulieren" können window.setTimeout.


1
Einerseits gefällt mir diese Antwort sehr gut, da es sich in der Tat um ein Warten handelt. Sie wird nicht mehr so ​​brauchbar, wenn Sie einen Wert zurückgeben möchten. Wenn Sie keinen Wert zurückgeben, bin ich mir nicht sicher, ob es einen realen Anwendungsfall für das Muster gibt?
Martin Meeser

Sie können natürlich ein Versprechen zurückgeben und die Funktion auf diese Weise implementieren. Dies würde jedoch im Allgemeinen eine Bibliothek eines Drittanbieters erfordern, die Versprechen oder Polyfill implementiert, es sei denn, Sie verwenden ECMA-262. Ohne ein Versprechen zurückzugeben, ist der beste Weg, einen Rückrufmechanismus zu verwenden, um dem Anrufer zu signalisieren, dass ein Ergebnis verfügbar ist.
Kiran

Bei Bedarf können Sie auch Parameter übergeben: stackoverflow.com/questions/1190642/…
SharpC

1
Das ist so eine tolle Antwort. Ich gab fast auf, nachdem ich mich in vielen Tech-Foren umgesehen hatte. Ich denke, es hat perfekt für mich funktioniert, weil ich einen Wert zurückgegeben habe. Es hat auch im Internet Explorer funktioniert. Vielen Dank.
Joseph

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

Verwenden:

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

15

Lösung mit Promise , async \ await und EventEmitter, die es ermöglicht, sofort auf Flaggenwechsel ohne Schleifen zu reagieren

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

EventEmitterist im Knoten eingebaut. Im Browser müssen Sie es selbst einbinden, beispielsweise mit diesem Paket: https://www.npmjs.com/package/eventemitter3


14

ES6 mit 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
Wie haben die Leute das vermisst
Aviad

10

Mit Ecma Script 2017 können Sie async-await und while zusammen verwenden, um dies zu tun. Und während das Programm nicht abstürzt oder sperrt, ist selbst eine Variable niemals wahr

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


7

Moderne Lösung mit Promise

myFunction() in der ursprünglichen Frage kann wie folgt geändert werden

async function myFunction(number) {

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

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

    ...
    ... do something

}

Wo until()ist diese Dienstprogrammfunktion?

function until(conditionFunction) {

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

  return new Promise(poll);
}

Einige Verweise auf asynchrone / warten- und Pfeilfunktionen befinden sich in einem ähnlichen Beitrag: https://stackoverflow.com/a/52652681/209794


4

Zum Durchlaufen von ($ .each) Objekten und Ausführen einer länger laufenden Operation (die verschachtelte Ajax-Synchronisierungsaufrufe enthält) für jedes Objekt:

Ich habe zuerst einen Brauch festgelegt done=false Eigenschaft für jede festgelegt.

Stellen Sie dann in einer rekursiven Funktion jede ein done=trueund verwenden Sie sie weiter setTimeout. (Diese Operation soll alle anderen Benutzeroberflächen stoppen, einen Fortschrittsbalken anzeigen und alle anderen Verwendungszwecke blockieren, damit ich mir die Synchronisierungsaufrufe verziehen kann.)

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

Ähnlich wie bei Lightbeards Antwort verwende ich den folgenden Ansatz

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

Ich habe versucht, den @ Kiran-Ansatz wie folgt zu verwenden:

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

(Framework, das ich benutze, zwingt mich, Funktionen auf diese Weise zu definieren). Aber ohne Erfolg, denn wenn die Ausführung zum zweiten Mal in die checkFlag-Funktion kommt, thisist dies nicht mein Objekt Window. Also habe ich mit dem Code unten fertig

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

1

Verwenden von nicht blockierendem Javascript mit der EventTarget-API

In meinem Beispiel muss ich auf einen Rückruf warten, bevor ich ihn verwenden kann. Ich habe keine Ahnung, wann dieser Rückruf eingestellt ist. Es kann vor oder nach der Ausführung sein. Und ich muss es möglicherweise mehrmals aufrufen (alles asynchron)

// 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

Es gibt ein Knotenpaket, das delaysehr einfach zu bedienen ist

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

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

1

Ich habe hier einen Ansatz in Anlehnung an die Callback-Lösungen gewählt, aber versucht, ihn etwas allgemeiner zu gestalten. Die Idee ist, dass Sie Funktionen hinzufügen, die Sie ausführen müssen, nachdem sich etwas in einer Warteschlange geändert hat. Wenn das passiert, durchlaufen Sie die Warteschlange, rufen die Funktionen auf und leeren die Warteschlange.

Funktion zur Warteschlange hinzufügen:

let _queue = [];

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

Führen Sie die Warteschlange aus und leeren Sie sie:

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

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

    _queue = [];
}

Und wenn Sie _addToQueue aufrufen, möchten Sie den Rückruf umbrechen:

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

Wenn Sie die Bedingung erfüllt haben, rufen Sie an _runQueue()

Dies war nützlich für mich, da ich mehrere Dinge hatte, die unter denselben Bedingungen warten mussten. Und es entkoppelt die Erkennung der Bedingung von allem, was ausgeführt werden muss, wenn diese Bedingung getroffen wird.


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

In meinem Beispiel protokolliere ich jede Sekunde einen neuen Zählerwert:

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


0

Wenn Sie Folgendes verwenden dürfen: In async/awaitIhrem Code können Sie Folgendes ausprobieren:

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 hier: https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts

Kopieren / Einfügen auf der Konsole : goahead = true.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.