Saya memiliki fungsi javascript seperti ini:
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{}
...
... do something
}
Masalahnya adalah bahwa javascript macet dan program saya macet. jadi pertanyaan saya adalah bagaimana saya bisa menunggu di tengah fungsi sampai flag benar tanpa "busy-wait"?
javascript
synchronization
ilay zeidman
sumber
sumber
jQuery.Deferred
,Q
,async
, ...Jawaban:
Karena javascript di browser adalah single thread (kecuali untuk webworkers yang tidak terlibat di sini) dan satu thread eksekusi javascript berjalan sampai selesai sebelum yang lain dapat berjalan, pernyataan Anda:
while(flag==false) {}
hanya akan berjalan selamanya (atau sampai browser mengeluh tentang loop javascript yang tidak responsif), halaman akan tampak digantung dan tidak ada javascript lain yang akan mendapatkan kesempatan untuk berjalan, sehingga nilai bendera tidak akan pernah dapat diubah.
Untuk penjelasan lebih lanjut, Javascript adalah bahasa yang digerakkan oleh peristiwa . Itu berarti bahwa ia menjalankan sepotong Javascript sampai ia mengembalikan kendali kembali ke penerjemah. Kemudian, hanya ketika kembali ke interpreter, Javascript mendapatkan event berikutnya dari antrian event dan menjalankannya.
Semua hal seperti pengatur waktu dan acara jaringan dijalankan melalui antrean acara. Jadi, ketika pengatur waktu aktif atau permintaan jaringan tiba, pengatur waktu tidak akan pernah "mengganggu" Javascript yang sedang berjalan. Alih-alih, sebuah acara dimasukkan ke dalam antrean acara Javascript dan kemudian, saat Javascript yang sedang berjalan selesai, acara berikutnya ditarik dari antrean acara dan gilirannya untuk dijalankan.
Jadi, ketika Anda melakukan pengulangan tak terbatas seperti
while(flag==false) {}
, Javascript yang sedang berjalan tidak pernah selesai dan dengan demikian kejadian berikutnya tidak pernah ditarik dari antrian kejadian dan dengan demikian nilaiflag
tidak pernah berubah. Kuncinya di sini adalah bahwa Javascript tidak didorong oleh interupsi . Ketika pengatur waktu menyala, itu tidak mengganggu Javascript yang sedang berjalan, menjalankan beberapa Javascript lain dan kemudian membiarkan Javascript yang sedang berjalan melanjutkan. Itu hanya dimasukkan ke dalam antrian acara menunggu sampai Javascript yang sedang berjalan selesai untuk mendapatkan gilirannya untuk dijalankan.Yang perlu Anda lakukan adalah memikirkan kembali cara kerja kode Anda dan menemukan cara berbeda untuk memicu kode apa pun yang ingin Anda jalankan saat
flag
nilainya berubah. Javascript dirancang sebagai bahasa yang digerakkan oleh peristiwa. Jadi, yang perlu Anda lakukan adalah mencari tahu peristiwa apa yang Anda minati sehingga Anda dapat mendengarkan peristiwa yang mungkin menyebabkan perubahan bendera dan Anda dapat memeriksa bendera pada peristiwa itu atau Anda dapat memicu peristiwa Anda sendiri dari kode apa pun yang mungkin mengubah bendera atau Anda dapat mengimplementasikan fungsi panggilan balik bahwa kode apa pun yang berubah bendera itu dapat memanggil callback Anda setiap kali bagian kode yang bertanggung jawab untuk mengubah nilai bendera akan mengubah nilainya menjaditrue
, itu hanya memanggil fungsi panggilan balik dan dengan demikian kode Anda yang ingin dijalankan saat bendera disetel ketrue
akan dijalankan pada waktu yang tepat. Ini jauh lebih efisien daripada mencoba menggunakan semacam pengatur waktu untuk terus-menerus memeriksa nilai bendera.function codeThatMightChangeFlag(callback) { // do a bunch of stuff if (condition happens to change flag value) { // call the callback to notify other code callback(); } }
sumber
Javascript adalah single threaded, karenanya merupakan perilaku pemblokiran halaman. Anda dapat menggunakan pendekatan ditangguhkan / janji yang disarankan oleh orang lain, tetapi cara yang paling dasar adalah menggunakan
window.setTimeout
. Misalnyafunction checkFlag() { if(flag == false) { window.setTimeout(checkFlag, 100); /* this checks the flag every 100 milliseconds*/ } else { /* do something*/ } } checkFlag();
Berikut adalah tutorial yang bagus dengan penjelasan lebih lanjut: Tutorial
EDIT
Seperti yang ditunjukkan orang lain, cara terbaik adalah menyusun ulang kode Anda untuk menggunakan callback. Namun, jawaban ini akan memberi Anda gambaran bagaimana Anda dapat 'mensimulasikan' perilaku asynchronous dengan
window.setTimeout
.sumber
Solusi menggunakan Promise , async \ await dan EventEmitter yang memungkinkan untuk bereaksi langsung pada perubahan bendera tanpa loop sama sekali
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
adalah node bawaan. Di browser Anda harus memasukkannya sendiri, misalnya menggunakan paket ini: https://www.npmjs.com/package/eventemitter3sumber
ES6 dengan 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)
sumber
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(); } }
Menggunakan:
waitFor(() => window.waitForMe, () => console.log('got you'))
sumber
Dengan Ecma Script 2017 Anda dapat menggunakan async-await dan sementara bersama-sama untuk melakukannya Dan sementara tidak akan merusak atau mengunci program bahkan variabel tidak pernah benar
//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 };
sumber
Solusi modern menggunakan Promise
myFunction()
dalam pertanyaan awal dapat dimodifikasi sebagai berikutasync function myFunction(number) { var x=number; ... ... more initializations await until(_ => flag == true); ... ... do something }
dimana
until()
fungsi utilitas inifunction until(conditionFunction) { const poll = resolve => { if(conditionFunction()) resolve(); else setTimeout(_ => poll(resolve), 400); } return new Promise(poll); }
Beberapa referensi ke async / await dan fungsi panah ada di pos serupa: https://stackoverflow.com/a/52652681/209794
sumber
Untuk melakukan iterasi lebih dari objek ($ .each) dan menjalankan operasi yang berjalan lama (berisi panggilan sinkronisasi ajax bertingkat) pada setiap objek:
Saya pertama kali menetapkan
done=false
properti khusus pada masing-masing.Kemudian, dalam fungsi rekursif, atur masing
done=true
- masing dan lanjutkan menggunakansetTimeout
. (Ini adalah operasi yang dimaksudkan untuk menghentikan semua UI lainnya, menampilkan bilah kemajuan dan memblokir semua penggunaan lainnya jadi saya memaafkan diri saya sendiri untuk panggilan sinkronisasi.)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) } }
sumber
Mirip dengan jawaban Lightbeard, saya menggunakan pendekatan berikut
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 }
sumber
Saya mencoba menggunakan pendekatan @Kiran seperti berikut:
checkFlag: function() { var currentObject = this; if(flag == false) { setTimeout(currentObject.checkFlag, 100); } else { /* do something*/ } }
(kerangka kerja yang saya gunakan memaksa saya untuk mendefinisikan fungsi dengan cara ini). Tetapi tidak berhasil karena ketika eksekusi masuk ke dalam fungsi checkFlag kedua kalinya,
this
itu bukan objek sayaWindow
. Jadi, saya selesai dengan kode di bawah inicheckFlag: function() { var worker = setInterval (function(){ if(flag == true){ /* do something*/ clearInterval (worker); } },100); }
sumber
menggunakan javascript non blocking dengan EventTarget API
Dalam contoh saya, saya harus menunggu panggilan balik sebelum menggunakannya. Saya tidak tahu kapan panggilan balik ini disetel. Bisa sebelum atau sesudah saya harus menjalankannya. Dan saya perlu menyebutnya beberapa kali (semuanya asinkron)
// 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);
sumber
ada paket node yang
delay
sangat mudah digunakanconst delay = require('delay'); (async () => { bar(); await delay(100); // Executed 100 milliseconds later baz(); })();
sumber
Jika Anda diizinkan untuk menggunakan:
async/await
pada kode Anda, Anda dapat mencoba yang ini: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 di sini: https://stackblitz.com/edit/typescript-bgtnhj?file=index.ts
Pada konsol, hanya copy / paste:
goahead = true
.sumber
Saya mengambil pendekatan di sepanjang garis solusi panggilan balik di sini, tetapi mencoba membuatnya sedikit lebih umum. Idenya adalah Anda menambahkan fungsi yang perlu Anda jalankan setelah ada perubahan pada antrian. Ketika hal itu terjadi, Anda kemudian mengulang antrian, memanggil fungsi dan mengosongkan antrian.
Tambahkan fungsi ke antrian:
let _queue = []; const _addToQueue = (funcToQ) => { _queue.push(funcToQ); }
Jalankan dan hapus antrian:
const _runQueue = () => { if (!_queue || !_queue.length) { return; } _queue.forEach(queuedFunc => { queuedFunc(); }); _queue = []; }
Dan saat Anda memanggil _addToQueue, Anda akan ingin menggabungkan callback:
_addToQueue(() => methodYouWantToCallLater(<pass any args here like you normally would>));
Bila Anda sudah memenuhi syarat, hubungi
_runQueue()
Ini berguna bagi saya karena saya memiliki beberapa hal yang perlu menunggu dalam kondisi yang sama. Dan itu memisahkan deteksi kondisi dari apa pun yang perlu dijalankan saat kondisi itu tercapai.
sumber
//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();
sumber
Dalam contoh saya, saya mencatat nilai penghitung baru setiap detik:
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"); });
sumber