javascript - 异步 - 如何使非阻塞的javascript代码?

如何创建一个简单的非阻塞的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代码成为非阻塞的?

时间:

带回调的SetTimeout是一种方法,


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


});



请参见http://jsfiddle.net/alnitak/x3bwjjo6/callback函数将变量设置为当前迭代计数,并且单独的setTimeout循环调用该变量的当前值。

你不能同时执行两个循环,请记住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文件,你只能将对象传递给他们。

但是,通过生成Blob文件,我设法使用了没有分隔文件的Web Workers,并且我也可以将它们传递给回调函数。


//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 `nnbla 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;


 } 


}

我知道的有两种方法可以做到这一点,一个是使用setTimeout (如果你在支持环境中执行这个操作,则为requestAnimationFrame),另外一种方法是使用一个web在单独的线程中完成阻塞逻辑,这样主UI线程就不会被阻塞。

使用requestAnimationFramesetTimeout


//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();


}

使用网络工作者:


/***** 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 worker解决方案,因为它需要一个单独的worker.js文件来承载工作逻辑。


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!");


})



...