Зачекайте, поки прапор = true


94

У мене така функція 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.03.14

1
Існує безліч підручників навколо опису перспективних реалізацій різних бібліотек, наприклад. jQuery.Deferred або Q . До речі, ваша основна проблема така ж, як і в цьому питанні .
Сірко

3
Для тих, хто читає це у 2018 році, Promises підтримуються усіма браузерами, крім Opera Mini та IE11.
Даніель Рейна,

Основна проблема полягає в тому, що неможливо виконати справді блокуюче (сну) очікування в однопотоковому js-події. Ви можете створити лише обробник очікування. докладніше: stackoverflow.com/questions/41842147 / ...
SalientBrain

Відповіді:


73

Оскільки 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();
    }
}

98

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. Без повернення обіцянки найкращим способом є використання механізму зворотного дзвінка, щоб сигналізувати абоненту про те, що результат є доступним.
Кіран

Крім того, можна передати параметри , якщо це необхідно: stackoverflow.com/questions/1190642 / ...
SharpC

1
Це така чудова відповідь. Я майже здавався після того, як перекопав багато технічних форумів. Я думаю, що це прекрасно спрацювало для мене, тому що я повертав цінність. Це також працювало і в Internet Explorer. Дякую купу.
Джозеф

18

Рішення з використанням 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 та стрілки містяться в подібному дописі: https://stackoverflow.com/a/52652681/209794


4

Для ітерації об'єктів ($ .each) та виконання довготривалої операції (що містить вкладені виклики синхронізації ajax) для кожного об'єкта:

Спочатку я встановив спеціальну done=falseвластивість для кожного.

Потім у рекурсивній функції встановіть кожну 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

Подібно до відповіді Лайтборода, я використовую такий підхід

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

Я взяв підхід, який відповідає рішенням зворотного виклику, але намагався зробити його трохи загальнішим. Ідея полягає в тому, що ви додаєте функції, які потрібно виконувати після того, як щось зміниться в черзі. Коли щось трапиться, ви прокручуєте чергу, викликаєте функції та звільняєте чергу.

Додати функцію в чергу:

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.