Espere até flag = true

90

Eu tenho uma função javascript como esta:

function myFunction(number) {

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

    ...
    ... do something

}

O problema é que o javascript travou no momento e travou meu programa. então minha pergunta é como posso esperar no meio da função até que o sinalizador seja verdadeiro sem "espera-ocupado"?

ilay zeidman
fonte
3
Use o padrão promessa para seus inicializações - pode ser encontrada em algum bibliotecas como jQuery.Deferred, Q, async, ...
Sirko
onde exatamente usar e como?
ilay zeidman
1
Existem muitos tutoriais sobre a descrição das implementações de promessa de várias bibliotecas, por exemplo. jQuery.Deferred ou Q . Aliás, seu problema subjacente é o mesmo desta questão .
Sirko,
3
Para quem está lendo isso em 2018, o Promises é compatível com todos os navegadores, exceto opera mini e IE11.
Daniel Reina
O principal problema é que é impossível bloquear (dormir) realmente esperar em js de thread único de evento diven. Você só pode criar um manipulador de espera. veja mais: stackoverflow.com/questions/41842147/…
SalientBrain

Respostas:

68

Como o javascript em um navegador tem um único thread (exceto para webworkers que não estão envolvidos aqui) e um thread de execução do javascript é executado até a conclusão antes que outro possa ser executado, sua declaração:

while(flag==false) {}

será simplesmente executado para sempre (ou até que o navegador reclame sobre um loop de javascript não responsivo), a página parecerá estar travada e nenhum outro javascript terá a chance de ser executado, portanto, o valor do sinalizador nunca poderá ser alterado.

Para um pouco mais de explicação, Javascript é uma linguagem orientada a eventos . Isso significa que ele executa um fragmento de Javascript até retornar o controle ao interpretador. Então, somente quando retorna ao interpretador, Javascript obtém o próximo evento da fila de eventos e o executa.

Todas as coisas, como temporizadores e eventos de rede, passam pela fila de eventos. Portanto, quando um cronômetro dispara ou chega uma solicitação de rede, ele nunca "interrompe" o Javascript em execução no momento. Em vez disso, um evento é colocado na fila de eventos Javascript e, em seguida, quando o Javascript atualmente em execução termina, o próximo evento é retirado da fila de eventos e chega a sua vez de ser executado.

Portanto, quando você faz um loop infinito como while(flag==false) {}, o Javascript em execução no momento nunca termina e, portanto, o próximo evento nunca é retirado da fila de eventos e, portanto, o valor de flagnunca é alterado. A chave aqui é que o Javascript não é conduzido por interrupções . Quando um cronômetro dispara, ele não interrompe o Javascript em execução no momento, execute algum outro Javascript e, em seguida, deixe o Javascript em execução continuar. Ele apenas é colocado na fila de eventos esperando até que o Javascript atualmente em execução esteja pronto para ser executado.


O que você precisa fazer é repensar como seu código funciona e encontrar uma maneira diferente de acionar qualquer código que você deseja executar, quando o será executado no momento certo. Isso é muito, muito mais eficiente do que tentar usar algum tipo de cronômetro para verificar constantemente o valor do sinalizador.flag valor mudar. Javascript é projetado como uma linguagem orientada a eventos. Então, o que você precisa fazer é descobrir em quais eventos você pode registrar um interesse para que possa ouvir o evento que pode causar a mudança do sinalizador e pode examinar o sinalizador nesse evento ou pode acionar seu próprio evento de qualquer código pode alterar o sinalizador ou você pode implementar uma função de retorno de chamada para que qualquer código que altere esse sinalizador possa chamar seu retorno de chamada sempre que o trecho de código responsável por alterar o valor do sinalizador altere seu valor para true, ele apenas chama a função de retorno de chamada e, portanto, seu código que quer ser executado quando o sinalizador for definido paratrue

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

Javascript é de thread único, daí o comportamento de bloqueio de página. Você pode usar a abordagem adiada / promessa sugerida por outros, mas a forma mais básica seria usar window.setTimeout. Por exemplo

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

Aqui está um bom tutorial com mais explicações: Tutorial

EDITAR

Como outros apontaram, a melhor maneira seria reestruturar seu código para usar callbacks. No entanto, esta resposta deve dar uma ideia de como você pode 'simular' um comportamento assíncrono com window.setTimeout.

Kiran
fonte
1
Embora, por um lado, eu realmente goste desta resposta porque é realmente um js 'esperar', ela se torna menos utilizável se você quiser retornar um valor. Se você não retornar um valor, não tenho certeza se há um caso de uso no mundo real para o padrão.
Martin Meeser de
É claro que você pode retornar uma promessa e implementar a função dessa maneira. No entanto, isso geralmente requer uma biblioteca de terceiros que implementa promessas ou polyfill, a menos que você esteja usando ECMA-262. Sem retornar uma promessa, a melhor maneira é usar um mecanismo de retorno de chamada para sinalizar ao chamador que um resultado está disponível.
Kiran
Você também pode passar parâmetros, se necessário: stackoverflow.com/questions/1190642/…
SharpC
1
Esta é uma ótima resposta. Eu estava quase desistindo depois de vasculhar muitos fóruns de tecnologia. Acho que funcionou perfeitamente para mim porque eu estava devolvendo um valor. Também funcionou no Internet Explorer também. Muito obrigado.
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();
    }
}

Usar:

waitFor(() => window.waitForMe, () => console.log('got you'))
Maciej Dudziński
fonte
15

Solução usando Promise , async \ await e EventEmitter que permite reagir imediatamente na mudança de sinalização sem qualquer tipo de loops.

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á embutido no nó. No navegador, você deve incluí-lo por conta própria, por exemplo, usando este pacote: https://www.npmjs.com/package/eventemitter3

Yaroslav Dobzhanskij
fonte
14

ES6 com 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)
Sussurrador de Código
fonte
1
Como as pessoas perderam isso
Aviad
11

Com Ecma Script 2017 você pode usar async-await e while juntos para fazer isso E o while não irá travar ou travar o programa, mesmo a variável nunca será verdadeira

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

Toprak
fonte
8

Solução moderna usando Promise

myFunction() na questão original pode ser modificado da seguinte forma

async function myFunction(number) {

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

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

    ...
    ... do something

}

onde until()está essa função de utilidade

function until(conditionFunction) {

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

  return new Promise(poll);
}

Algumas referências às funções async / await e arrow estão em uma postagem semelhante: https://stackoverflow.com/a/52652681/209794

Lightbeard
fonte
4

Para iterar sobre objetos ($ .each) e executar uma operação de longa duração (contendo chamadas de sincronização ajax aninhadas) em cada objeto:

Eu primeiro defini uma done=falsepropriedade customizada em cada um.

Então, em uma função recursiva, configure cada um done=truee continue usando setTimeout. (É uma operação destinada a interromper todas as outras IU, mostrar uma barra de progresso e bloquear todos os outros usos, então me perdoei pelas chamadas de sincronização.)

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)
        }
}
Rodney
fonte
1

Semelhante à resposta do Lightbeard, eu uso a seguinte abordagem

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
}
Chris_F
fonte
1

Tentei usar a abordagem @Kiran como segue:

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

(framework que estou usando me força a definir funções desta forma). Mas sem sucesso porque quando a execução vem dentro da função checkFlag pela segunda vez, thisnão é meu objeto, é Window. Então, terminei com o código abaixo

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

usando JavaScript sem bloqueio com API EventTarget

No meu exemplo, preciso aguardar um retorno de chamada antes de usá-lo. Não tenho ideia de quando esse retorno de chamada está definido. Pode ser antes ou depois de eu precisar executá-lo. E posso precisar chamá-lo várias vezes (tudo assíncrono)

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

Melvbob
fonte
1

existe um pacote de nós delaymuito fácil de usar

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

    // Executed 100 milliseconds later
    baz();
})();
Eric
fonte
1

Adotei uma abordagem semelhante às soluções de retorno de chamada aqui, mas tentei torná-la um pouco mais genérica. A ideia é adicionar funções que você precisa executar depois que algo muda em uma fila. Quando a coisa acontece, você faz um loop pela fila, chama as funções e esvazia a fila.

Adicionar função à fila:

let _queue = [];

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

Execute e libere a fila:

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

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

    _queue = [];
}

E quando você invocar _addToQueue, você desejará envolver o retorno de chamada:

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

Quando você cumprir a condição, ligue _runQueue()

Isso foi útil para mim porque tinha várias coisas que precisavam esperar na mesma condição. E separa a detecção da condição de tudo o que precisa ser executado quando essa condição é atingida.

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

Avinash Maurya
fonte
0

No meu exemplo, eu registro um novo valor de contador a cada segundo:

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

xinthose
fonte
0

Se você tiver permissão para usar: async/awaitem seu código, poderá tentar este:

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

Demonstração aqui: https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts

No console, basta copiar / colar: goahead = true.

Viewsonic
fonte