単純な非ブロックJavaScript関数呼び出しを行うにはどうすればよいですか?例えば:
_ //begin the program
console.log('begin');
nonBlockingIncrement(10000000);
console.log('do more stuff');
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n){
var i=0;
while(i<n){
i++;
}
console.log('0 incremented to '+i);
}
_
出力
_"beginPage"
"0 incremented to 10000000"
"do more stuff"
_
この単純なループを形成して非同期に実行し、コールバック関数を介して結果を出力するにはどうすればよいですか?アイデアは、「その他の作業」をブロックしないことです。
_"beginPage"
"do more stuff"
"0 incremented to 10000000"
_
コールバックと継続に関するチュートリアルに従ってみましたが、それらはすべて外部ライブラリまたは関数に依存しているようです。それらのどれも真空の質問に答えません:ノンブロッキングになるようにJavascriptコードをどのように書くのですか!?
私は尋ねる前にこの答えを非常に一生懸命探しました。見ていないと思い込まないでください。私が見つけたものはすべてNode.js固有です( [1] 、 [2] 、 [3] 、 [4] 、 [5] )、または他の関数またはライブラリに固有のその他( [6] 、 [7] 、 [ 8] 、 [9] 、 [10] 、 [11] )、特にJQueryとsetTimeout()
。 JQueryやNodeのようなJavascriptで記述されたツールではなく、Javascriptを使用してノンブロッキングコードを記述してください。 問題を重複としてマークする前に、問題を読み直してください。
コールバックを使用したSetTimeoutを使用する方法です。ただし、関数スコープはC#や他のマルチスレッド環境と同じではないことを理解してください。
Javascriptは、関数のコールバックが完了するまで待機しません。
あなたが言うなら:
function doThisThing(theseArgs) {
setTimeout(function (theseArgs) { doThatOtherThing(theseArgs); }, 1000);
alert('hello world');
}
渡された関数が実行される前にアラートが発生します。
違いは、アラートがスレッドをブロックしたことですが、コールバックはそうではありませんでした。
ループをブロックしないようにするには、ループをセクションに分割し、JSイベント処理ループが次のセクションに進む前にユーザーイベントを消費できるようにする必要があります。
これを実現する最も簡単な方法は、一定量の作業を行ってから、setTimeout(..., 0)
を使用して次の作業をキューに入れることです。重要なのは、そのキューイングにより、JSイベントループは、次の作業に進む前にその間にキューに入れられたイベントを処理できるようにすることです。
function yieldingLoop(count, chunksize, callback, finished) {
var i = 0;
(function chunk() {
var end = Math.min(i + chunksize, count);
for ( ; i < end; ++i) {
callback.call(null, i);
}
if (i < count) {
setTimeout(chunk, 0);
} else {
finished.call(null);
}
})();
}
使用法:
yieldingLoop(1000000, 1000, function(i) {
// use i here
}, function() {
// loop done here
});
callback
関数が変数を現在の値に設定するだけのデモについては、 http://jsfiddle.net/alnitak/x3bwjjo6/ を参照してください。繰り返しカウント、および別のsetTimeout
ベースのループがその変数の現在の値をポーリングし、その値でページを更新します。
2つのループを同時に実行することはできません。JSはシングルスレッドであることに注意してください。
つまり、これを実行しても機能しません
function loopTest() {
var test = 0
for (var i; i<=100000000000, i++) {
test +=1
}
return test
}
setTimeout(()=>{
//This will block everything, so the second won't start until this loop ends
console.log(loopTest())
}, 1)
setTimeout(()=>{
console.log(loopTest())
}, 1)
マルチスレッドを実現するには、Web Workersを使用する必要がありますですが、分離されたjsファイルが必要であり、オブジェクトのみを渡すことができます。
しかし、Web Workersを使用することに成功しました Blobファイルを生成することでファイルを分離せずに、コールバック関数も渡すことができます。
//A fileless Web Worker
class ChildProcess {
//@param {any} ags, Any kind of arguments that will be used in the callback, functions too
constructor(...ags) {
this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
}
//@param {function} cb, To be executed, the params must be the same number of passed in the constructor
async exec(cb) {
var wk_string = this.worker.toString();
wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));
var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
var wk = new Worker(wk_link);
wk.postMessage({ callback: cb.toString(), args: this.args });
var resultado = await new Promise((next, error) => {
wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
wk.onerror = e => error(e.message);
})
wk.terminate(); window.URL.revokeObjectURL(wk_link);
return resultado
}
worker() {
onmessage = async function (e) {
try {
var cb = new Function(`return ${e.data.callback}`)();
var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);
try {
var result = await cb.apply(this, args); //If it is a promise or async function
return postMessage(result)
} catch (e) { throw new Error(`CallbackError: ${e}`) }
} catch (e) { postMessage({error: e.message}) }
}
}
}
setInterval(()=>{console.log('Not blocked code ' + Math.random())}, 1000)
console.log("starting blocking synchronous code in Worker")
console.time("\nblocked");
var proc = new ChildProcess(blockCpu, 43434234);
proc.exec(function(block, num) {
//This will block for 10 sec, but
block(10000) //This blockCpu function is defined below
return `\n\nbla bla ${num}\n` //Captured in the resolved promise
}).then(function (result){
console.timeEnd("\nblocked")
console.log("End of blocking code", result)
})
.catch(function(error) { console.log(error) })
//random blocking function
function blockCpu(ms) {
var now = new Date().getTime();
var result = 0
while(true) {
result += Math.random() * Math.random();
if (new Date().getTime() > now +ms)
return;
}
}
私が知る限り、これを行うには一般に2つの方法があります。 1つはsetTimeout
(またはサポート環境でこれを行う場合はrequestAnimationFrame
)を使用することです。 @Alnitakは別の回答でこれを行う方法を示しました。別の方法は、Webワーカーを使用して別のスレッドでブロッキングロジックを終了し、メインUIスレッドがブロックされないようにすることです。
requestAnimationFrame
またはsetTimeout
を使用:
//begin the program
console.log('begin');
nonBlockingIncrement(100, function (currentI, done) {
if (done) {
console.log('0 incremented to ' + currentI);
}
});
console.log('do more stuff');
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n, callback){
var i = 0;
function loop () {
if (i < n) {
i++;
callback(i, false);
(window.requestAnimationFrame || window.setTimeout)(loop);
}
else {
callback(i, true);
}
}
loop();
}
Web Workerを使用する:
/***** Your worker.js *****/
this.addEventListener('message', function (e) {
var i = 0;
while (i < e.data.target) {
i++;
}
this.postMessage({
done: true,
currentI: i,
caller: e.data.caller
});
});
/***** Your main program *****/
//begin the program
console.log('begin');
nonBlockingIncrement(100, function (currentI, done) {
if (done) {
console.log('0 incremented to ' + currentI);
}
});
console.log('do more stuff');
// Create web worker and callback register
var worker = new Worker('./worker.js'),
callbacks = {};
worker.addEventListener('message', function (e) {
callbacks[e.data.caller](e.data.currentI, e.data.done);
});
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n, callback){
const caller = 'nonBlockingIncrement';
callbacks[caller] = callback;
worker.postMessage({
target: n,
caller: caller
});
}
ワーカーロジックをホストするために別のworker.js
ファイルが必要なため、Webワーカーソリューションを実行できません。
JQueryを使用している場合、 Alnitak's answer の遅延実装を作成しました
function deferredEach (arr, batchSize) {
var deferred = $.Deferred();
var index = 0;
function chunk () {
var lastIndex = Math.min(index + batchSize, arr.length);
for(;index<lastIndex;index++){
deferred.notify(index, arr[index]);
}
if (index >= arr.length) {
deferred.resolve();
} else {
setTimeout(chunk, 0);
}
};
setTimeout(chunk, 0);
return deferred.promise();
}
次に、返されたpromiseを使用して、進行状況と完了したコールバックを管理できます。
var testArray =["Banana", "Orange", "Apple", "Mango"];
deferredEach(testArray, 2).progress(function(index, item){
alert(item);
}).done(function(){
alert("Done!");
})