Attendez que flag = true


90

J'ai une fonction javascript comme celle-ci:

function myFunction(number) {

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

    ...
    ... do something

}

Le problème est que le javascript est bloqué pendant un certain temps et bloqué mon programme. donc ma question est de savoir comment puis-je attendre au milieu de la fonction jusqu'à ce que l'indicateur soit vrai sans "busy-wait"?


3
Utilisez le modèle de promesse pour vos initialisations - se trouvent dans des bibliothèques tout à fait comme jQuery.Deferred, Q, async...
Sirko

où exactement l'utiliser et comment?
ilay zeidman

1
Il existe de nombreux didacticiels décrivant les implémentations promises des différentes bibliothèques, par exemple. jQuery.Deferred ou Q . Btw, votre problème sous-jacent est le même que dans cette question .
Sirko

3
Pour ceux qui liront ceci en 2018, les promesses sont prises en charge par tous les navigateurs à part opera mini et IE11.
Daniel Reina

Le principal problème est qu'il est impossible de faire une attente réellement bloquante (sommeil) dans des js monothread pilotés par événement. Vous pouvez uniquement créer un gestionnaire d'attente. voir plus: stackoverflow.com/questions/41842147/…
SalientBrain

Réponses:


68

Étant donné que javascript dans un navigateur est à thread unique (sauf pour les webworkers qui ne sont pas impliqués ici) et qu'un thread d'exécution de javascript s'exécute jusqu'à la fin avant qu'un autre puisse s'exécuter, votre déclaration:

while(flag==false) {}

fonctionnera simplement pour toujours (ou jusqu'à ce que le navigateur se plaint d'une boucle javascript non réactive), la page semblera bloquée et aucun autre javascript n'aura jamais la possibilité de s'exécuter, la valeur de l'indicateur ne pourra donc jamais être modifiée.

Pour un peu plus d'explications, Javascript est un langage événementiel . Cela signifie qu'il exécute un morceau de Javascript jusqu'à ce qu'il renvoie le contrôle à l'interpréteur. Ensuite, seulement lorsqu'il revient à l'interpréteur, Javascript récupère l'événement suivant de la file d'attente d'événements et l'exécute.

Toutes les choses comme les minuteries et les événements réseau passent par la file d'attente des événements. Ainsi, lorsqu'une minuterie se déclenche ou qu'une requête réseau arrive, elle n'interrompt jamais le Javascript en cours d'exécution. Au lieu de cela, un événement est placé dans la file d'attente d'événements Javascript, puis, lorsque le Javascript en cours d'exécution se termine, l'événement suivant est extrait de la file d'attente d'événements et il est exécuté à son tour.

Ainsi, lorsque vous effectuez une boucle infinie telle que while(flag==false) {}, le Javascript en cours d'exécution ne se termine jamais et donc l'événement suivant n'est jamais extrait de la file d'attente d'événements et donc la valeur de flagne change jamais. La clé ici est que Javascript n'est pas piloté par interruption . Lorsqu'un minuteur se déclenche, il n'interrompt pas le Javascript en cours d'exécution, exécute un autre Javascript et laisse le Javascript en cours d'exécution continuer. Il est simplement placé dans la file d'attente d'événements en attendant que le Javascript en cours d'exécution soit terminé pour s'exécuter à son tour.


Ce que vous devez faire est de repenser le fonctionnement de votre code et de trouver une manière différente de déclencher le code que vous souhaitez exécuter lorsque la flagvaleur change. Javascript est conçu comme un langage événementiel. Donc, ce que vous devez faire est de déterminer les événements pour lesquels vous pouvez enregistrer un intérêt afin que vous puissiez soit écouter l'événement qui pourrait provoquer le changement du drapeau et vous pouvez examiner le drapeau sur cet événement ou vous pouvez déclencher votre propre événement à partir de quel que soit le code qui pourrait changer l'indicateur ou vous pouvez implémenter une fonction de rappel que quel que soit le code qui change, cet indicateur peut appeler votre rappel chaque fois que le morceau de code responsable de la modification de la valeur de l'indicateur changerait sa valeur en true, il appelle simplement la fonction de rappel et donc votre code qui veut s'exécuter lorsque l'indicateur est défini surtruearrivera à fonctionner au bon moment. C'est beaucoup plus efficace que d'essayer d'utiliser une sorte de minuterie pour vérifier constamment la valeur de l'indicateur.

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

96

Javascript est mono-thread, d'où le comportement de blocage de page. Vous pouvez utiliser l'approche différée / promesse suggérée par d'autres, mais la manière la plus élémentaire serait de l'utiliser window.setTimeout. Par exemple

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

Voici un bon tutoriel avec des explications supplémentaires: Tutoriel

ÉDITER

Comme d'autres l'ont souligné, le meilleur moyen serait de restructurer votre code pour utiliser des rappels. Cependant, cette réponse devrait vous donner une idée de la façon dont vous pouvez «simuler» un comportement asynchrone avec window.setTimeout.


1
Alors que d'une part j'aime beaucoup cette réponse car c'est en effet un js 'wait' elle devient moins utilisable si vous souhaitez retourner une valeur. Si vous ne renvoyez pas de valeur, je ne suis pas sûr qu'il existe un cas d'utilisation réel pour le modèle?
Martin Meeser

Vous pouvez bien sûr renvoyer une promesse et implémenter la fonction de cette manière. Cependant, cela nécessiterait généralement une bibliothèque tierce qui implémente des promesses ou un polyfill, sauf si vous utilisez ECMA-262. Sans retourner une promesse, le meilleur moyen est d'utiliser un mécanisme de rappel pour signaler à l'appelant qu'un résultat est disponible.
Kiran

Vous pouvez également passer des paramètres si nécessaire: stackoverflow.com/questions/1190642/…
SharpC

1
C'est une si bonne réponse. J'abandonnais presque après avoir fouillé dans de nombreux forums techniques. Je pense que cela a parfaitement fonctionné pour moi car je retournais une valeur. Cela a également fonctionné sur Internet Explorer. Merci beaucoup.
Joseph

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

Utilisation:

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

16

Solution utilisant Promise , async \ await et EventEmitter qui permet de réagir immédiatement au changement de drapeau sans aucun type de boucle

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 intégré au node. Dans le navigateur, vous devrez l'inclure vous-même, par exemple en utilisant ce package: https://www.npmjs.com/package/eventemitter3


14

ES6 avec 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
Comment les gens ont-ils manqué ça
Aviad

11

Avec Ecma Script 2017, vous pouvez utiliser async-await et tout ensemble pour le faire Et bien que ne plantera pas ou ne verrouille pas le programme, même la variable ne sera jamais vraie

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

Solution moderne utilisant Promise

myFunction() dans la question d'origine peut être modifié comme suit

async function myFunction(number) {

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

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

    ...
    ... do something

}

until()est cette fonction utilitaire

function until(conditionFunction) {

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

  return new Promise(poll);
}

Certaines références aux fonctions async / await et arrow sont dans un article similaire: https://stackoverflow.com/a/52652681/209794


4

Pour itérer sur des objets ($ .each) et exécuter une opération de longue durée (contenant des appels de synchronisation ajax imbriqués) sur chaque objet:

J'ai d'abord défini une done=falsepropriété personnalisée sur chacun.

Ensuite, dans une fonction récursive, définissez chacun done=trueet continuez à utiliser setTimeout. (C'est une opération destinée à arrêter toutes les autres interfaces utilisateur, afficher une barre de progression et bloquer toute autre utilisation, donc je me suis pardonné pour les appels de synchronisation.)

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

Semblable à la réponse de Lightbeard, j'utilise l'approche suivante

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

J'ai essayé d'utiliser l'approche @Kiran comme suit:

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

(le cadre que j'utilise me force à définir les fonctions de cette façon). Mais sans succès car lorsque l'exécution entre dans la fonction checkFlag la deuxième fois, ce thisn'est pas mon objet Window. Donc, j'ai fini avec le code ci-dessous

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

1

utilisation de javascript non bloquant avec l' API EventTarget

Dans mon exemple, je dois attendre un rappel avant de l'utiliser. Je n'ai aucune idée du moment où ce rappel est défini. Cela peut être avant ou après avoir besoin de l'exécuter. Et je peux avoir besoin de l'appeler plusieurs fois (tout est asynchrone)

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

il existe un package de nœuds delaytrès facile à utiliser

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

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

1

J'ai adopté une approche similaire aux solutions de rappel ici, mais j'ai essayé de la rendre un peu plus générique. L'idée est que vous ajoutez des fonctions que vous devez exécuter après que quelque chose change dans une file d'attente. Lorsque cela se produit, vous parcourez la file d'attente, appelez les fonctions et videz la file d'attente.

Ajouter une fonction à la file d'attente:

let _queue = [];

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

Exécutez et videz la file d'attente:

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

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

    _queue = [];
}

Et lorsque vous appelez _addToQueue, vous voudrez encapsuler le rappel:

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

Lorsque vous avez rempli la condition, appelez _runQueue()

Cela m'a été utile car j'avais plusieurs choses à attendre dans la même condition. Et il dissocie la détection de la condition de tout ce qui doit être exécuté lorsque cette condition est atteinte.


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

Dans mon exemple, j'enregistre une nouvelle valeur de compteur toutes les secondes:

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

Si vous êtes autorisé à utiliser: async/awaitsur votre code, vous pouvez essayer celui-ci:

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

Démo ici: https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts

Sur la console, il suffit de copier / coller: goahead = true.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.