У меня есть такая функция javascript:
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{}
...
... do something
}
Проблема в том, что javascript застрял на время и застрял в моей программе. Итак, мой вопрос: как я могу подождать в середине функции, пока флаг не станет истинным без «занято-подождите»?
javascript
synchronization
Илай Зейдман
источник
источник
jQuery.Deferred
,Q
,async
, ...Ответы:
Поскольку 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(); } }
источник
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
.источник
Решение с использованием 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)
источник
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 и одновременно делать это вместе. И пока не произойдет сбой или блокировка программы, даже переменная никогда не будет истинной.
//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 };
источник
Современное решение с использованием 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
источник
Для перебора ($ .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) } }
источник
Как и в ответе 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); }
источник
с использованием неблокирующего 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);
источник
есть пакет узлов,
delay
очень простой в использованииconst delay = require('delay'); (async () => { bar(); await delay(100); // Executed 100 milliseconds later baz(); })();
источник
Если вам разрешено использовать:
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
.источник
Я использовал подход, аналогичный решениям обратного вызова здесь, но попытался сделать его более общим. Идея состоит в том, что вы добавляете функции, которые необходимо выполнять после того, как что-то изменится в очереди. Когда это происходит, вы перебираете очередь, вызываете функции и очищаете очередь.
Добавить функцию в очередь:
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()
Это было полезно для меня, потому что у меня было несколько вещей, которые нужно было подождать при одинаковых условиях. И он отделяет обнаружение условия от того, что нужно выполнить при достижении этого условия.
источник
//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"); });
источник