私はこのようなjavascript関数を持っています:
function myFunction(number) {
var x=number;
...
... more initializations
//here need to wait until flag==true
while(flag==false)
{}
...
... do something
}
問題は、javascriptがその間スタックしていて、プログラムがスタックしていることです。だから私の質問は、「ビジー待機」なしでフラグがtrueになるまで関数の途中でどのように待つことができますか?
ブラウザのjavascriptはシングルスレッド(ここでは関係ないWebworkerを除く)であり、javascript実行の1つのスレッドが完了してから別のスレッドが実行されるため、ステートメントは次のようになります。
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();
詳細な説明のある良いチュートリアルです: Tutorial
編集
他の人が指摘したように、最良の方法はコールバックを使用するようにコードを再構築することです。ただし、この回答は、window.setTimeout
を使用して非同期動作を「シミュレート」する方法を示しています。
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'))
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/eventemitter
Async/Awaitを使用するES6
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)
($ .each)オブジェクトを反復処理し、各オブジェクトで長時間実行される操作(ネストされたajax同期呼び出しを含む)を実行するには:
まず、それぞれにカスタムdone=false
プロパティを設定します。
次に、再帰関数で各done=true
を設定し、setTimeout
を使用して続行します。 (これは操作ですmeant他のすべてのUIを停止し、進行状況バーを表示し、他のすべての使用をブロックするので、同期呼び出しを自分で許しました。)
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)
}
}
Ecma Script 2017では、async-awaitとwhileを一緒に使用してそれを行うことができます
//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およびarrow関数へのいくつかの参照は同様の投稿にあります: https://stackoverflow.com/a/52652681/209794
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関数内に2回入ったとき、this
は私のオブジェクトではなくWindow
であるため、成功しません。だから、私は以下のコードで終了しました
checkFlag: function() {
var worker = setInterval (function(){
if(flag == true){
/* do something*/
clearInterval (worker);
}
},100);
}
私の例では、毎秒新しいカウンター値を記録します。
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");
});
//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();
EventTarget API でノンブロッキングJavaScriptを使用する
私の例では、使用する前にコールバックを待つ必要があります。このコールバックがいつ設定されるのかわかりません。それを実行する必要がある前または後にすることができます。そして、私はそれを数回呼び出す必要があります(すべて非同期)
// 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);