รอจนกว่า flag = true


96

ฉันมีฟังก์ชันจาวาสคริปต์ดังนี้:

function myFunction(number) {

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

    ...
    ... do something

}

ปัญหาคือ javascript ค้างใน while และติดโปรแกรมของฉัน คำถามของฉันคือฉันจะรอตรงกลางของฟังก์ชันจนกว่าแฟล็กจะเป็นจริงโดยไม่ "ไม่ว่างรอ" ได้อย่างไร


3
ใช้รูปแบบสัญญาสำหรับ initializations คุณ - สามารถพบได้ในค่อนข้างบางห้องสมุดชอบjQuery.Deferred, Q, async...
Sirko

จะใช้ที่ไหนและอย่างไร
ilay zeidman

1
มีบทเรียนมากมายเกี่ยวกับการอธิบายการใช้งานตามคำสัญญาของห้องสมุดต่างๆเช่น jQuery.DeferredหรือQ Btw ปัญหาพื้นฐานของคุณเหมือนกับในคำถามนี้
Sirko

3
สำหรับคนที่อ่านสิ่งนี้ในปี 2018 Promises ได้รับการสนับสนุนโดยเบราว์เซอร์ทั้งหมดนอกเหนือจาก opera mini และ IE11
Daniel Reina

ปัญหาหลักคือเป็นไปไม่ได้ที่จะทำการปิดกั้นอย่างแท้จริง (สลีป) รอใน js เธรดเดี่ยว คุณสามารถสร้างเครื่องจัดการการรอได้เท่านั้น ดูเพิ่มเติม: stackoverflow.com/questions/41842147/…
SalientBrain

คำตอบ:


74

เนื่องจากจาวาสคริปต์ในเบราว์เซอร์เป็นเธรดเดียว (ยกเว้นสำหรับผู้ทำงานบนเว็บที่ไม่ได้เกี่ยวข้องกับที่นี่) และเธรดหนึ่งของการเรียกใช้จาวาสคริปต์จะทำงานจนเสร็จสิ้นก่อนที่อีกเธรดหนึ่งจะทำงานได้คำสั่งของคุณ:

while(flag==false) {}

จะทำงานตลอดไป (หรือจนกว่าเบราว์เซอร์จะบ่นเกี่ยวกับการวนซ้ำจาวาสคริปต์ที่ไม่ตอบสนอง) หน้าเว็บจะหยุดทำงานและไม่มีจาวาสคริปต์อื่น ๆ ที่จะได้รับโอกาสในการเรียกใช้ดังนั้นค่าของค่าสถานะจะไม่สามารถเปลี่ยนแปลงได้

สำหรับคำอธิบายเพิ่มเติมเล็กน้อยJavascript เป็นภาษาเหตุการณ์ที่ขับเคลื่อนด้วย นั่นหมายความว่าจะเรียกใช้ Javascript ชิ้นหนึ่งจนกว่าจะส่งคืนการควบคุมกลับไปที่ล่าม จากนั้นเมื่อส่งกลับไปที่ล่ามเท่านั้น Javascript จะรับเหตุการณ์ถัดไปจากคิวเหตุการณ์และเรียกใช้งาน

ทุกสิ่งเช่นตัวจับเวลาและเหตุการณ์เครือข่ายจะทำงานผ่านคิวเหตุการณ์ ดังนั้นเมื่อตัวจับเวลาเริ่มทำงานหรือมีคำขอเครือข่ายมาถึงตัวจับเวลาจะไม่ "ขัดจังหวะ" Javascript ที่กำลังทำงานอยู่ เหตุการณ์จะถูกใส่ไว้ในคิวเหตุการณ์ Javascript แทนจากนั้นเมื่อ Javascript ที่รันอยู่ในปัจจุบันเสร็จสิ้นเหตุการณ์ถัดไปจะถูกดึงออกจากคิวเหตุการณ์และจะเปิดให้รัน

ดังนั้นเมื่อคุณทำวนซ้ำแบบไม่สิ้นสุดเช่นwhile(flag==false) {}Javascript ที่กำลังทำงานอยู่จะไม่สิ้นสุดดังนั้นเหตุการณ์ถัดไปจะไม่ถูกดึงออกจากคิวเหตุการณ์ดังนั้นค่าของการflagไม่เคยเปลี่ยนแปลง พวกเขาที่สำคัญที่นี่เป็นที่จาวาสคริปต์ที่ไม่ได้ขับเคลื่อนการขัดจังหวะ เมื่อตัวจับเวลาเริ่มทำงานจะไม่ขัดจังหวะ 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();
    }
}

99

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
ในแง่หนึ่งฉันชอบคำตอบนี้มากเพราะมันเป็นการ 'รอ' จริงๆมันจะไม่สามารถใช้งานได้หากคุณต้องการคืนค่า หากคุณไม่คืนค่าฉันไม่แน่ใจว่ามีการใช้งานจริงสำหรับรูปแบบนี้หรือไม่?
Martin Meeser

แน่นอนคุณสามารถคืนคำสัญญาและใช้ฟังก์ชันในลักษณะนั้นได้ อย่างไรก็ตามโดยทั่วไปแล้วสิ่งนี้จะต้องใช้ไลบรารีของบุคคลที่สามที่ดำเนินการตามสัญญาหรือโพลีฟิลเว้นแต่คุณจะใช้ ECMA-262 โดยไม่ต้องคืนสัญญาวิธีที่ดีที่สุดคือใช้กลไกการโทรกลับเพื่อส่งสัญญาณไปยังผู้โทรว่ามีผลลัพธ์
Kiran

คุณยังสามารถส่งผ่านพารามิเตอร์ได้หากต้องการ: stackoverflow.com/questions/1190642/…
SharpC

1
นี่คือคำตอบที่ยอดเยี่ยม ฉันเกือบจะยอมแพ้หลังจากขุดผ่านฟอรัมเทคโนโลยีมากมาย ฉันคิดว่ามันทำงานได้ดีสำหรับฉันเพราะฉันได้คืนค่า นอกจากนี้ยังทำงานบน Internet Explorer ได้อีกด้วยขอบคุณมาก
โจเซฟ

19

การแก้ปัญหาโดยใช้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


17

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)

1
ผู้คนพลาดสิ่งนี้ได้อย่างไร
Aviad

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

11

ด้วย Ecma Script 2017 คุณสามารถใช้ async-await และในขณะที่ร่วมกันทำเช่นนั้นและในขณะที่จะไม่ผิดพลาดหรือล็อคโปรแกรมแม้ตัวแปรจะไม่เป็นจริง

//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, ไม่ได้เป็นวัตถุของฉันมันเป็นthis Windowฉันเขียนโค้ดด้านล่างเสร็จแล้ว

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

1

โดยใช้จาวาสคริปต์ที่ไม่ปิดกั้นด้วยEventTarget API

ในตัวอย่างของฉันฉันต้องรอการติดต่อกลับก่อนจึงจะใช้งานได้ ฉันไม่รู้ว่าเมื่อใดที่ตั้งค่าการโทรกลับนี้ อาจเป็นก่อนหลังฉันต้องดำเนินการ และฉันต้องเรียกมันหลาย ๆ ครั้ง (ทุกอย่างไม่ตรงกัน)

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

หากคุณได้รับอนุญาตให้ใช้: 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วาง:


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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.