Chờ cho đến khi flag = true


96

Tôi có chức năng javascript như thế này:

function myFunction(number) {

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

    ...
    ... do something

}

Vấn đề là javascript bị kẹt trong lúc đó và làm kẹt chương trình của tôi. vì vậy câu hỏi của tôi là làm thế nào tôi có thể đợi ở giữa hàm cho đến khi cờ là true mà không "bận-đợi"?


3
Sử dụng mô hình hứa hẹn cho khởi tạo của bạn - có thể được tìm thấy trong một số thư viện khá thích jQuery.Deferred, Q, async, ...
Sirko

chính xác để sử dụng nó ở đâu và như thế nào?
ilay zeidman

1
Có rất nhiều hướng dẫn xung quanh việc mô tả việc triển khai hứa hẹn của các thư viện khác nhau, ví dụ. jQuery.Deferred hoặc Q . Btw, vấn đề cơ bản của bạn cũng giống như trong câu hỏi này .
Sirko

3
Đối với ai đó đang đọc cuốn sách này vào năm 2018, Promises được hỗ trợ bởi tất cả các trình duyệt ngoài opera mini và IE11.
Daniel Reina

Vấn đề chính là không thể thực hiện thực sự chặn (ngủ) chờ trong js đơn luồng sự kiện. Bạn chỉ có thể tạo trình xử lý chờ. xem thêm: stackoverflow.com/questions/41842147/…
SalientBrain

Câu trả lời:


74

Vì javascript trong một trình duyệt là một luồng (ngoại trừ những người làm web không tham gia ở đây) và một luồng thực thi javascript sẽ hoàn thành trước khi một luồng khác có thể chạy, câu lệnh của bạn:

while(flag==false) {}

chỉ đơn giản là sẽ chạy mãi mãi (hoặc cho đến khi trình duyệt phàn nàn về vòng lặp javascript không đáp ứng), trang sẽ xuất hiện ở trạng thái treo và không javascript nào khác có cơ hội chạy, do đó giá trị của cờ không bao giờ có thể thay đổi.

Để giải thích thêm một chút, Javascript là một ngôn ngữ hướng sự kiện . Điều đó có nghĩa là nó chạy một đoạn Javascript cho đến khi nó trả lại quyền điều khiển cho trình thông dịch. Sau đó, chỉ khi nó quay trở lại trình thông dịch, Javascript mới lấy sự kiện tiếp theo từ hàng đợi sự kiện và chạy nó.

Tất cả những thứ như bộ hẹn giờ và sự kiện mạng đều chạy qua hàng đợi sự kiện. Vì vậy, khi bộ hẹn giờ kích hoạt hoặc một yêu cầu mạng đến, nó không bao giờ "làm gián đoạn" Javascript hiện đang chạy. Thay vào đó, một sự kiện được đưa vào hàng đợi sự kiện Javascript và sau đó, khi Javascript hiện đang chạy kết thúc, sự kiện tiếp theo sẽ được kéo từ hàng đợi sự kiện và đến lượt nó chạy.

Vì vậy, khi bạn thực hiện một vòng lặp vô hạn chẳng hạn như while(flag==false) {}, Javascript hiện đang chạy sẽ không bao giờ kết thúc và do đó sự kiện tiếp theo không bao giờ được kéo ra khỏi hàng đợi sự kiện và do đó giá trị của flagkhông bao giờ bị thay đổi. Điều quan trọng ở đây là Javascript không được điều khiển gián đoạn . Khi bộ đếm thời gian kích hoạt, nó không làm gián đoạn Javascript hiện đang chạy, hãy chạy một số Javascript khác và sau đó để Javascript hiện đang chạy tiếp tục. Nó chỉ được đưa vào hàng đợi sự kiện chờ cho đến khi Javascript hiện đang chạy hoàn tất để đến lượt nó chạy.


Những gì bạn cần làm là suy nghĩ lại cách mã của bạn hoạt động và tìm một cách khác để kích hoạt bất kỳ mã nào bạn muốn chạy khi flaggiá trị thay đổi. Javascript được thiết kế như một ngôn ngữ hướng sự kiện. Vì vậy, những gì bạn cần làm là tìm ra những sự kiện mà bạn có thể đăng ký quan tâm để bạn có thể lắng nghe sự kiện có thể khiến cờ thay đổi và bạn có thể kiểm tra cờ trên sự kiện đó hoặc bạn có thể kích hoạt sự kiện của riêng mình từ bất kỳ mã nào có thể thay đổi cờ hoặc bạn có thể triển khai một hàm gọi lại mà bất kỳ mã nào thay đổi mà cờ đó có thể gọi lệnh gọi lại của bạn bất cứ khi nào đoạn mã chịu trách nhiệm thay đổi giá trị cờ sẽ thay đổi giá trị của truenó, nó chỉ gọi hàm gọi lại và do đó mã của bạn muốn chạy khi cờ được đặt thànhtruesẽ chạy vào đúng thời điểm. Điều này hiệu quả hơn nhiều so với việc cố gắng sử dụng một số loại bộ đếm thời gian để liên tục kiểm tra giá trị cờ.

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 là một chuỗi đơn, do đó có hành vi chặn trang. Bạn có thể sử dụng phương pháp trì hoãn / hứa hẹn do người khác đề xuất, nhưng cách cơ bản nhất sẽ là sử dụng window.setTimeout. Ví dụ

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

Đây là một hướng dẫn tốt với giải thích thêm: Hướng dẫn

BIÊN TẬP

Như những người khác đã chỉ ra, cách tốt nhất là cấu trúc lại mã của bạn để sử dụng lệnh gọi lại. Tuy nhiên, câu trả lời này sẽ cung cấp cho bạn ý tưởng về cách bạn có thể 'mô phỏng' một hành vi không đồng bộ với window.setTimeout.


1
Mặc dù một mặt, tôi thực sự thích câu trả lời này bởi vì nó thực sự là một js 'chờ đợi', nó không thể sử dụng được nếu bạn muốn trả về một giá trị. Nếu bạn không trả về một giá trị, tôi không chắc có cách sử dụng thế giới thực cho mẫu không?
Martin Meeser

Tất nhiên, bạn có thể trả về một lời hứa và triển khai chức năng theo cách đó. Tuy nhiên, điều này thường yêu cầu thư viện của bên thứ ba triển khai các hứa hẹn hoặc polyfill, trừ khi bạn đang sử dụng ECMA-262. Nếu không trả lại lời hứa, cách tốt nhất là sử dụng cơ chế gọi lại để báo hiệu cho người gọi biết rằng đã có kết quả.
Kiran

Bạn cũng có thể vượt qua các thông số nếu có yêu cầu: stackoverflow.com/questions/1190642/...
SharpC

1
Đây là một câu trả lời tuyệt vời. Tôi gần như bỏ cuộc sau khi đào bới nhiều diễn đàn công nghệ. Tôi nghĩ nó hoàn toàn phù hợp với tôi vì tôi đang trả lại một giá trị. Nó cũng hoạt động trên Internet Explorer. Cảm ơn rất nhiều.
Joseph

19

Giải pháp sử dụng Promise , async \ await và EventEmitter cho phép phản ứng ngay lập tức khi thay đổi cờ mà không cần bất kỳ loại vòng lặp nào

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

EventEmitterlà nội trang trong nút. Trong trình duyệt, bạn cần phải tự mình đưa nó vào, chẳng hạn như sử dụng gói này: https://www.npmjs.com/package/eventemitter3


17

ES6 với 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
Làm thế nào mà người bỏ lỡ này
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();
    }
}

Sử dụng:

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

11

Với Ecma Script 2017 Bạn có thể sử dụng async-await và while cùng nhau để làm điều đó Và while sẽ không bị sập hoặc khóa chương trình, thậm chí biến không bao giờ đúng

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

Giải pháp hiện đại sử dụng Promise

myFunction() trong câu hỏi ban đầu có thể được sửa đổi như sau

async function myFunction(number) {

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

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

    ...
    ... do something

}

until()chức năng tiện ích này ở đâu

function until(conditionFunction) {

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

  return new Promise(poll);
}

Một số tham chiếu đến hàm async / await và arrow trong một bài đăng tương tự: https://stackoverflow.com/a/52652681/209794


4

Để lặp lại các đối tượng ($ .each) và thực hiện một hoạt động lâu dài (chứa các lệnh gọi đồng bộ ajax lồng nhau) trên mỗi đối tượng:

Đầu tiên tôi đặt một thuộc tính tùy chỉnh done=falsetrên mỗi.

Sau đó, trong một hàm đệ quy, hãy đặt từng hàm done=truevà tiếp tục sử dụng setTimeout. (Đó là một hoạt động có nghĩa là dừng tất cả các giao diện người dùng khác, hiển thị thanh tiến trình và chặn tất cả các hoạt động sử dụng khác, vì vậy tôi đã tha thứ cho bản thân về các cuộc gọi đồng bộ.)

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

Tương tự như câu trả lời của Lightbeard, tôi sử dụng cách tiếp cận sau

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

Tôi đã cố gắng sử dụng phương pháp @Kiran như sau:

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

(khung mà tôi đang sử dụng buộc tôi phải xác định các chức năng theo cách này). Nhưng không thành công vì khi thực thi đến bên trong hàm checkFlag lần thứ hai, thisnó không phải là đối tượng của tôi Window. Vì vậy, tôi đã hoàn thành với mã bên dưới

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

1

sử dụng javascript không chặn với EventTarget API

Trong ví dụ của tôi, tôi cần đợi cuộc gọi lại trước khi sử dụng nó. Tôi không biết khi nào cuộc gọi lại này được thiết lập. Nó có thể là trước sau khi tôi cần thực hiện nó. Và tôi có thể cần gọi nó vài lần (mọi thứ không đồng bộ)

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

có một gói nút delayrất dễ sử dụng

const delay = require('delay');

(async () => {
    bar();

    await delay(100);

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

1

Nếu bạn được phép sử dụng: async/awaittrên mã của mình, bạn có thể thử cái này:

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

Demo tại đây: https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts

Trên giao diện điều khiển, chỉ cần sao chép / dán: goahead = true.


1

Tôi đã thực hiện một cách tiếp cận dọc theo các dòng của các giải pháp gọi lại ở đây, nhưng cố gắng làm cho nó chung chung hơn một chút. Ý tưởng là bạn thêm các chức năng mà bạn cần thực thi sau khi một thứ gì đó thay đổi vào hàng đợi. Khi điều đó xảy ra, sau đó bạn lặp qua hàng đợi, gọi các hàm và làm trống hàng đợi.

Thêm chức năng vào hàng đợi:

let _queue = [];

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

Thực thi và xóa hàng đợi:

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

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

    _queue = [];
}

Và khi bạn gọi _addToQueue, bạn sẽ muốn kết thúc lệnh gọi lại:

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

Khi bạn đã đáp ứng điều kiện, hãy gọi _runQueue()

Điều này rất hữu ích cho tôi vì tôi có một số thứ cần phải chờ trong cùng một điều kiện. Và nó tách việc phát hiện điều kiện khỏi bất cứ điều gì cần được thực hiện khi điều kiện đó được thực hiện.


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

Trong ví dụ của tôi, tôi ghi lại một giá trị bộ đếm mới mỗi giây:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.