flag = trueになるまで待つ


90

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

function myFunction(number) {

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

    ...
    ... do something

}

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


3
あなたの初期化のための約束のパターンを使用して-かなりの図書館で見つけることができますが好きjQuery.DeferredQasync、...
Sirko

それをどこでどのように使用するのですか?
ilay zeidman

1
さまざまなライブラリのpromise実装について説明するチュートリアルはたくさんあります。jQuery.DeferredまたはQ。ところで、根本的な問題はこの質問と同じです。
シルコ

3
2018年にこれを読む人にとって、Promiseはopera miniとIE11以外のすべてのブラウザーでサポートされています。
ダニエルレイナ

主な問題は、イベント分割されたシングルスレッドjsで真にブロッキング(スリープ)待機を行うことが不可能であることです。作成できるのは待機ハンドラのみです。もっと見る:stackoverflow.com/questions/41842147/...
SalientBrain

回答:


68

ブラウザのJavaScriptはシングルスレッドであり(ここに関係しないWebワーカーを除く)、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();
    }
}

96

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

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

詳細な説明がある優れたチュートリアルを次に示します。チュートリアル

編集

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


1
一方で私はこの答えが本当に好きですが、これは実際にはjsの「待機」であるため、値を返したい場合はそれほど使用できなくなります。値を返さない場合、パターンの実際のユースケースがあるかどうかはわかりません。
Martin Meeser 16

もちろん、promiseを返し、その方法で関数を実装することができます。ただし、ECMA-262を使用している場合を除き、これには通常、Promiseまたはポリフィルを実装するサードパーティライブラリが必要です。promiseを返さない場合の最良の方法は、コールバックメカニズムを使用して、結果が利用可能であることを発信者に通知することです。
キラン

また、必要に応じてパラメータを渡すことができます:stackoverflow.com/questions/1190642/...を
SharpC

1
これは素晴らしい答えです。多くの技術フォーラムを掘り下げた後、私はほとんどあきらめていました。私は値を返していたので、それは完全に私のために働いたと思います。Internet Explorerでも動作しました。ありがとうございました。
ジョセフ

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

使用する:

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

15

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


14

ES6 with 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
どのように人がこれを見逃してなかった
Aviad

11

Ecma Script 2017を使用すると、async-awaitとwhileを一緒に使用してそれを行うことができます。しかもwhileは、プログラムがクラッシュしたりロックされたりせず、変数がtrueになることはありません

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

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


4

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

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

次に、再帰関数でそれぞれdone=trueを設定し、を使い続けsetTimeoutます。(それは、操作の意味他のすべての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)
        }
}

1

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

私は以下のような@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

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


1

delay非常に使いやすいノードパッケージがあります

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

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

1

私はここでコールバックソリューションの線に沿ってアプローチしましたが、もう少し一般的なものにしようとしました。アイデアは、何かがキューに変更された後に実行する必要がある関数を追加することです。事態が発生すると、キューをループして関数を呼び出し、キューを空にします。

キューに関数を追加:

let _queue = [];

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

キューを実行してフラッシュします。

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

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

    _queue = [];
}

_addToQueueを呼び出すときは、コールバックをラップする必要があります。

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

条件を満たしたら、電話して _runQueue()

同じ状況で待たなければならないものがいくつかあったので、これは私にとって便利でした。そして、その条件が検出されたときに実行する必要があるものから、条件の検出を切り離します。


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

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

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

あなたが使用を許可されている場合:async/awaitあなたのコードで、これを試すことができます:

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

ここのデモ:https : //stackblitz.com/edit/typescript-bgtnhj?file=index.ts

コンソールで、単にコピー/貼り付け:goahead = true

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.