How to make non-blocking javascript code?
如何进行简单的非块Javascript函数调用? 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | //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); } |
输出
1 2 3 | "beginPage" "0 incremented to 10000000" "do more stuff" |
如何形成这个简单的循环以异步执行并通过回调函数输出结果? 这个想法是不阻止"做更多的东西":
1 2 3 | "beginPage" "do more stuff" "0 incremented to 10000000" |
我已经尝试过关于回调和延续的教程,但它们似乎都依赖于外部库或函数。 他们都没有在真空中回答这个问题:如何编写Javascript代码是非阻塞的??
在询问之前,我非常努力地寻找这个答案; 请不要以为我没看。 我发现的一切都是Node.js特定的([1],[2],[3],[4],[5])或其他特定于其他函数或库([6],[7],[8], [9],[10],[11]),特别是JQuery和
要使循环无阻塞,必须将其分成几个部分,并允许JS事件处理循环在继续执行下一部分之前使用用户事件。
实现这一目标的最简单方法是执行一定量的工作,然后使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); } })(); } |
用法:
1 2 3 4 5 | yieldingLoop(1000000, 1000, function(i) { // use i here }, function() { // loop done here }); |
请参阅http://jsfiddle.net/alnitak/x3bwjjo6/以获取演示,其中
带回调的SetTimeout是要走的路。但是,要了解您的功能范围与C#或其他多线程环境中的功能范围不同。
Javascript不会等待你的函数的回调完成。
如果你说:
1 2 3 4 | function doThisThing(theseArgs) { setTimeout(function (theseArgs) { doThatOtherThing(theseArgs); }, 1000); alert('hello world'); } |
在您通过的功能之前,您的警报将会触发。
区别在于警报阻止了线程,但是你的回调没有。
据我所知,通常有两种方法可以做到这一点。一种是使用
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /***** 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 }); } |
您无法运行Web工作器解决方案,因为它需要单独的
你不能同时执行两个循环,记住JS是单线程。
所以,这样做永远不会奏效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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文件,并且只能将对象传递给它们。
但是,我通过生成Blob文件设法使用没有分离文件的Web Workers,我也可以通过它们回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | //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(" blocked"); 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 ` bla bla ${num} ` //Captured in the resolved promise }).then(function (result){ console.timeEnd(" blocked") 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; } } |
如果您正在使用jQuery,我创建了Alnitak答案的延迟实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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来管理进度并完成回调:
1 2 3 4 5 6 | var testArray =["Banana","Orange","Apple","Mango"]; deferredEach(testArray, 2).progress(function(index, item){ alert(item); }).done(function(){ alert("Done!"); }) |