簡體   English   中英

如何等到 JavaScript 中的謂詞條件變為真?

[英]How to wait until a predicate condition becomes true in JavaScript?

我有這樣的 javascript function:

function myFunction(number) {

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

    ...
    ... do something

}

問題是 javascript 卡在了 while 里,卡住了我的程序。 所以我的問題是我如何在 function 中間等待,直到標志為真而沒有“忙等待”?

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來“模擬”異步行為。

因為瀏覽器中的 javascript 是單線程的(此處未涉及的 webworkers 除外),並且一個 javascript 執行線程在另一個線程可以運行之前運行到完成,所以您的聲明:

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

使用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

ES6 與 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)

使用 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 和箭頭函數的一些引用: https ://stackoverflow.com/a/52652681/209794

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

使用 Ecma Script 2017 您可以使用 async-await 和 while 一起執行此操作而且 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 };

我通過實現以下方法解決了這個問題。

const waitUntil = (condition) => {
    return new Promise((resolve) => {
        let interval = setInterval(() => {
            if (!condition()) {
                return
            }

            clearInterval(interval)
            resolve()
        }, 100)
    })
}

現在,每當您想等到滿足某個條件時,您都可以這樣調用它。

await waitUntil(() => /* your condition */)

對於迭代 ($.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)
        }
}

如果你被允許在你的代碼上使用: 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

使用帶有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);

最干凈的解決方案(@tdxius 解決方案的改進)基於受控時間間隔循環、承諾和超時來拒絕承諾並在給定時間內不滿足條件的情況下清除間隔

const waitUntil = (condition) => {
                    return new Promise((resolve, reject) => {
                        const interval = setInterval(() => {
                            if (!condition()) {
                                return;
                            }

                            clearInterval(interval);
                            resolve();
                        }, 100);

                        setTimeout(() => {
                            clearInterval(interval);
                            reject('your error msg');
                        }, 5000);
                    });
                };

現在,每當您想等到滿足某個條件時,您都可以這樣調用它。

waitUntil(CONDITION_FUNCTION)
  .then(() => DO_SOMETHING)
  .catch((YOUR_ERROR_MSG) => console.warn(YOUR_ERROR_MSG))

與 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
}

我嘗試使用@Kiran 方法,如下所示:

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

(我使用的框架迫使我以這種方式定義函數)。 但沒有成功,因為當第二次執行進入 checkFlag 函數時, this不是我的對象,它是Window 所以,我完成了下面的代碼

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

有一個node打包delay非常好用

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

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

我在這里采取了一種類似於回調解決方案的方法,但試圖讓它更通用一點。 這個想法是您添加在隊列發生更改后需要執行的功能。 當事情發生時,您將遍歷隊列,調用函數並清空隊列。

將函數添加到隊列:

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

這對我很有用,因為我有幾件事需要在相同的條件下等待。 並且它將條件檢測與滿足條件時需要執行的任何操作分離。

盡量避免 while 循環,因為它可能會阻塞你的代碼,使用異步和承諾。

剛剛寫了這個庫:

https://www.npmjs.com/package/utilzed

有一個函數waitForTrue

import utilzed from 'utilzed'

const checkCondition = async () => {
  // anything that you are polling for to be expecting to be true
  const response = await callSomeExternalApi();
  return response.success;
}

// this will waitForTrue checkCondition to be true
// checkCondition will be called every 100ms
const success = await utilzed.waitForTrue(100, checkCondition, 1000);

if (success) {
  // Meaning checkCondition function returns true before 1000 ms
  return;
}

// meaning after 1000ms the checkCondition returns false still
// handle unsuccessful "poll for true" 

有沒有人想過這樣做?

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, 2000);
  });
}




function myFunction(number) {

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

    ...
    ... do something

}

TMCDR; (= "太多代碼...沒讀")

在調用代碼的可讀性和實現代碼的簡潔性方面最簡單:

const until = (predicate) => {
  const poll = (done) => (predicate ? done() : setTimeout(() => poll(done), 500));
  return new Promise(poll);
};

調用者:

await until(someConditional());

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

在我的示例中,我每秒記錄一個新的計數器值:

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

現代而簡單的解決方案

async function waitUntil(condition, time = 100) {
    while (!condition()) {
        await new Promise((resolve) => setTimeout(resolve, time));
    }
}

用法

async function foo() {
  await waitUntil(() => flag === true);
  console.log('condition is met!');
}

受 jfriend00 的啟發,這對我有用

 const seconds = new Date(); // wait 5 seconds for flag to become true const waitTime = 5 const extraSeconds = seconds.setSeconds(seconds.getSeconds() + waitTime); while (Date.now() < extraSeconds) { // break when flag is false if (flag === false) break; }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM