web-dev-qa-db-ja.com

flag = trueになるまで待つ

私はこのようなjavascript関数を持っています:

function myFunction(number) {

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

    ...
    ... do something

}

問題は、javascriptがその間スタックしていて、プログラムがスタックしていることです。だから私の質問は、「ビジー待機」なしでフラグがtrueになるまで関数の途中でどのように待つことができますか?

61
ilay zeidman

ブラウザのjavascriptはシングルスレッド(ここでは関係ないWebworkerを除く)であり、javascript実行の1つのスレッドが完了してから別のスレッドが実行されるため、ステートメントは次のようになります。

while(flag==false) {}

単に永久に(またはブラウザーが応答しないjavascriptループについて不平を言うまで)実行され、ページがハングしているように見え、他のjavascriptが実行される機会がないため、フラグの値を変更することはできません。

もう少し説明するために、Javascriptはイベント駆動型言語です。つまり、インタプリタに制御を戻すまで、Javascriptを実行します。次に、インタプリタに戻ったときにのみ、Javascriptはイベントキューから次のイベントを取得して実行します。

タイマーやネットワークイベントなどはすべて、イベントキューを介して実行されます。そのため、タイマーが作動したり、ネットワーク要求が到着したりしても、現在実行中のJavascriptを「中断」することはありません。代わりに、イベントがJavascriptイベントキューに入れられ、現在実行中のJavascriptが終了すると、次のイベントがイベントキューからプルされ、実行される順番が取得されます。

したがって、while(flag==false) {}などの無限ループを実行すると、現在実行中のJavascriptは終了せず、次のイベントはイベントキューからプルされず、したがってflagの値は変更されません。ここで重要なのは、Javascriptは割り込み駆動型ではないです。タイマーが起動すると、現在実行中のJavascriptが中断されることはなく、他のJavascriptが実行され、現在実行中のJavascriptが続行されます。現在実行中のJavascriptが実行されて実行されるまで、イベントキューに入れられます。


あなたがする必要があるのは、コードの仕組みを再考し、flag値が変更されたときに実行したいコードをトリガーする別の方法を見つけることです。 Javascriptは、イベント駆動型言語として設計されています。そのため、フラグを変更する可能性のあるイベントをリッスンし、そのイベントのフラグを調べるか、または独自のイベントをトリガーできるように、関心を登録できるイベントを把握する必要がありますフラグを変更する可能性のあるコード、またはフラグの値を変更するコードが値をtrueに変更するたびにそのフラグを変更するコードがコールバックを呼び出すことができるコールバック関数を実装できます。関数、したがって、フラグがtrueに設定されたときに実行するコードは、適切なタイミングで実行されます。これは、何らかのタイマーを使用してフラグ値を絶えずチェックしようとするよりもはるかに効率的です。

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

Javascriptはシングルスレッドであるため、ページブロックの動作です。他の人が提案した遅延/約束アプローチを使用できますが、最も基本的な方法はwindow.setTimeoutを使用することです。例えば。

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

詳細な説明のある良いチュートリアルです: Tutorial

編集

他の人が指摘したように、最良の方法はコールバックを使用するようにコードを再構築することです。ただし、この回答は、window.setTimeoutを使用して非同期動作を「シミュレート」する方法を示しています。

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

つかいます:

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

Promise、async\await、およびEventEmitterを使用してすぐに反応できるソリューションループの種類なしでフラグが変更されたとき

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

EventEmitterはビルトインノードです。ブラウザでは、たとえばこのパッケージを使用して、自分でそれを含める必要があります。 https://www.npmjs.com/package/eventemitter

Async/Awaitを使用するES6

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)
4
Code Whisperer

($ .each)オブジェクトを反復処理し、各オブジェクトで長時間実行される操作(ネストされたajax同期呼び出しを含む)を実行するには:

まず、それぞれにカスタムdone=falseプロパティを設定します。

次に、再帰関数で各done=trueを設定し、setTimeoutを使用して続行します。 (これは操作ですmeant他のすべてのUIを停止し、進行状況バーを表示し、他のすべての使用をブロックするので、同期呼び出しを自分で許しました。)

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)
        }
}
4
Rodney

Ecma Script 2017では、async-awaitとwhileを一緒に使用してそれを行うことができます

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

Promiseを使用した最新のソリューション

元の質問のmyFunction()は、次のように変更できます

async function myFunction(number) {

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

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

    ...
    ... do something

}

ここで、until()はこのユーティリティ関数です

function until(conditionFunction) {

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

  return new Promise(poll);
}

Async/awaitおよびarrow関数へのいくつかの参照は同様の投稿にあります: https://stackoverflow.com/a/52652681/209794

1
Lightbeard

Lightbeardの答えと同様に、私は次のアプローチを使用します

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
Chris_F

私は次のような@Kiranアプローチを使用しようとしました:

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

(私が使用しているフレームワークは、この方法で関数を定義することを強制します)。しかし、実行がcheckFlag関数内に2回入ったとき、thisは私のオブジェクトではなくWindowであるため、成功しません。だから、私は以下のコードで終了しました

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

私の例では、毎秒新しいカウンター値を記録します。

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
xinthose
//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
Avinash Maurya

EventTarget API でノンブロッキングJavaScriptを使用する

私の例では、使用する前にコールバックを待つ必要があります。このコールバックがいつ設定されるのかわかりません。それを実行する必要がある前または後にすることができます。そして、私はそれを数回呼び出す必要があります(すべて非同期)

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