J'ai javascript fonction comme ceci:
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é dans le temps et bloqué mon programme. Ma question est donc la suivante: comment puis-je attendre au milieu de la fonction jusqu'à ce que le drapeau devienne vrai sans "Occuper-attendre"?
Parce que javascript dans un navigateur est à thread unique (sauf pour les travailleurs Web qui ne sont pas impliqués ici) et qu'un thread d'exécution de javascript s'exécute complètement avant qu'un autre ne puisse être exécuté, votre déclaration:
while(flag==false) {}
sera simplement exécuté pour toujours (ou jusqu'à ce que le navigateur se plaint d'une boucle javascript non sensible), la page semblera être bloquée et aucun autre javascript n'aura jamais l'occasion de s'exécuter; la valeur du drapeau ne pourra donc jamais être modifiée.
Pour un peu plus d'explications,Javascript est un langage piloté par les événements. Cela signifie qu'il exécute un morceau de Javascript jusqu'à ce qu'il rende le contrôle à l'interprète. Ensuite, uniquement lorsqu'il retourne à l'interpréteur, Javascript extrait l'événement suivant de la file d'attente et l'exécute.
Toutes les choses telles que les minuteries et les événements réseau passent par la file d'attente d'événements. Ainsi, lorsqu'une minuterie se déclenche ou qu'une requête réseau arrive, elle n'interrompt jamais le code Javascript en cours d'exécution. À la place, un événement est placé dans la file d'attente d'événements Javascript, puis, lorsque le code Javascript en cours d'exécution est terminé, le prochain événement est extrait de la file d'attente d'événements et son tour est exécuté.
Ainsi, lorsque vous effectuez une boucle infinie telle que while(flag==false) {}
, le code Javascript en cours d'exécution ne se termine jamais. Ainsi, l'événement suivant n'est jamais extrait de la file d'attente et la valeur de flag
n'est jamais modifiée. La clé ici est queJavascript n’est pas guidé par des interruptions. Quand une minuterie se déclenche, elle n'interrompt pas le Javascript en cours d'exécution, lance un autre Javascript et laisse ensuite le JavaScript en cours continuer. Il est simplement placé dans la file d'attente des événements en attendant que le javascript en cours d'exécution soit terminé pour pouvoir s'exécuter à son tour.
Ce que vous devez faire, c'est repenser le fonctionnement de votre code et trouver un moyen différent de déclencher le code que vous souhaitez exécuter lorsque la valeur flag
change. Javascript est conçu comme un langage événementiel. Vous devez donc déterminer quels sont les événements pour lesquels vous pouvez enregistrer un intérêt. Vous pouvez soit écouter l’événement qui pourrait entraîner le changement de drapeau, soit examiner le drapeau de cet événement, soit déclencher votre propre événement. quel que soit le code qui puisse changer l’indicateur ou vous pouvez implémenter une fonction de rappel indiquant que quel que soit le code modifié, cet indicateur peut appeler votre rappel chaque fois que le code responsable de la modification de la valeur de l’indicateur change sa valeur en true
, il appelle simplement la fonction de rappel et le code qui veut être exécuté lorsque l'indicateur est défini sur true
sera exécuté au bon moment. C'est beaucoup, beaucoup plus efficace que d'essayer d'utiliser une sorte de minuteur pour vérifier en permanence la valeur du drapeau.
function codeThatMightChangeFlag(callback) {
// do a bunch of stuff
if (condition happens to change flag value) {
// call the callback to notify other code
callback();
}
}
Javascript est à thread unique, d’où le comportement de blocage des pages. Vous pouvez utiliser l’approche différée/promesse suggérée par d’autres, mais la méthode la plus simple consiste à 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: Tutorial
MODIFIER
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 manière dont vous pouvez "simuler" un comportement asynchrone avec window.setTimeout
.
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'))
Pour parcourir plusieurs objets ($ .each) et exécuter une opération longue (contenant des appels imbriqués ajax sync) sur chaque objet:
J'ai d'abord défini une propriété done=false
personnalisée sur chacun.
Ensuite, dans une fonction récursive, définissez chaque done=true
et continuez avec setTimeout
. (C'est une opération signifiait pour arrêter toute autre interface utilisateur, afficher une barre de progression et bloquer toute autre utilisation afin que je me pardonne 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)
}
}
Avec Ecma Script 2017, vous pouvez utiliser async-wait et pendant que vous le faites ensemble.
//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
};
Solution utilisant Promise, async\wait et EventEmitter qui permet de réagir immédiatement au changement d'indicateur sans aucun type de boucle.
const EventEmitter = require('events');
const lock = false;
const bus = new EventEmitter();
async function lockable() {
if (lock) await new Promise(resolve => bus.once('unlocked', resolve));
....
lock = true;
...some logic....
lock = false;
bus.emit('unlocked');
}
EventEmitter
est intégré dans le noeud. Dans le navigateur, vous devrez l’inclure vous-même, par exemple en utilisant ce paquet: https://www.npmjs.com/package/eventemitter3
ES6 avec Async/En attente,
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)
Solution moderne utilisant Promise
myFunction()
dans la question initiale peut être modifié comme suit
async function myFunction(number) {
var x=number;
...
... more initializations
await until(_ => flag == true);
...
... do something
}
où 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/wait et arrow se trouvent dans un article similaire: https://stackoverflow.com/a/52652681/209794
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 framework que j'utilise me force à définir les fonctions de cette façon) . Mais sans succès car lorsque l'exécution vient à l'intérieur de la fonction checkFlag une seconde fois, this
n'est pas mon objet mais Window
.
checkFlag: function() {
var worker = setInterval (function(){
if(flag == true){
/* do something*/
clearInterval (worker);
}
},100);
}
Dans mon exemple, j'enregistre une nouvelle valeur de compteur chaque seconde:
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");
});
//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();
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
}
utilisation de javascript non bloquant avec API EventTarget
Dans mon exemple, je dois attendre un rappel avant de l'utiliser. Je ne sais pas quand ce rappel est défini. Cela peut être avant ou après que je dois l'exécuter. Et je peux avoir besoin de l'appeler plusieurs fois (tout est async)
// 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);