Подождите, пока flag = true


96

У меня есть такая функция javascript:

function myFunction(number) {

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

    ...
    ... do something

}

Проблема в том, что javascript застрял на время и застрял в моей программе. Итак, мой вопрос: как я могу подождать в середине функции, пока флаг не станет истинным без «занято-подождите»?


3
Используйте шаблон обещание для ваших инициализаций - можно найти во вполне некоторых библиотеках нравится jQuery.Deferred, Q, async, ...
Сирко

где именно использовать и как?
ilay zeidman 02

1
Существует множество руководств по описанию реализации обещаний различных библиотек, например. jQuery.Deferred или Q . Кстати, ваша основная проблема такая же, как в этом вопросе .
Sirko

3
Для тех, кто читает это в 2018 году, обещания поддерживаются всеми браузерами, кроме Opera mini и IE11.
Даниэль Рейна

Основная проблема заключается в том, что в однопоточных js-файлах с обработкой событий невозможно выполнить по-настоящему блокирующее (спящее) ожидание. Вы можете создать только обработчик ожидания. подробнее: stackoverflow.com/questions/41842147/…
SalientBrain 01

Ответы:


74

Поскольку javascript в браузере является однопоточным (за исключением веб-работников, которые здесь не участвуют), и один поток выполнения 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();
    }
}

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
Хотя, с одной стороны, мне очень нравится этот ответ, потому что это действительно js-ожидание, он становится не очень удобным, если вы хотите вернуть значение. Если вы не вернете значение, я не уверен, что этот шаблон можно использовать в реальном мире?
Мартин Мизер

Конечно, вы можете вернуть обещание и реализовать функцию таким образом. Однако для этого обычно требуется сторонняя библиотека, которая реализует обещания или полифил, если вы не используете 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
Как люди пропустили это
Авиад

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 и стрелки находятся в аналогичном сообщении: https://stackoverflow.com/a/52652681/209794


4

Для перебора ($ .each) объектов и выполнения длительной операции (содержащей вложенные вызовы синхронизации ajax) для каждого объекта:

Сначала я устанавливаю настраиваемое done=falseсвойство для каждого.

Затем в рекурсивной функции установите each done=trueи продолжайте использовать setTimeout. (Это операция, предназначенная для остановки всего остального пользовательского интерфейса, отображения индикатора выполнения и блокировки любого другого использования, поэтому я простил себя за вызовы синхронизации.)

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

с использованием неблокирующего javascript с 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");
});

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.