Comment faire du code javascript non-bloquant?
Comment puis-je faire un simple appel de fonction Javascript sans bloc? Par exemple:
//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);
}
sorties
"beginPage"
"0 incremented to 10000000"
"do more stuff"
Comment puis-je former cette boucle simple pour exécuter asynchrone et afficher les résultats via une fonction de rappel? L'idée est de ne pas bloquer "faire plus de choses":
"beginPage"
"do more stuff"
"0 incremented to 10000000"
j'ai essayé de suivre des tutoriels sur les callbacks et les continuations, mais ils semblent tous compter sur des bibliothèques ou des fonctions externes. Aucun d'entre eux ne répond à la question dans le vide: Comment écrire du code Javascript pour être non-bloquant!?
j'ai cherché très fort cette réponse avant de la Demander; ne présumez pas que je n'ai pas cherché. Tout ce que j'ai trouvé est un noeud.js specific ( [1] , [2] , [3] , [4] , [5] ) ou autrement spécifique à autres fonctions ou bibliothèques ( [6] , [7] , [8] , [9] , [10] , [11] ), notamment JQuery et setTimeout()
. S'il vous plaît aidez-moi à écrire le code non-bloquant en utilisant Javascript , pas les outils Javascript-écrits comme jQuery et noeud. veuillez relire la question avant de la marquer comme une copie.
5 réponses
SetTimeout avec des rappels est la solution. Bien que, comprendre vos portées de fonction ne sont pas les mêmes que dans C# ou un autre environnement multi-threaded.
Javascript n'attend pas la fin du rappel de votre fonction.
si vous dites:
function doThisThing(theseArgs) {
setTimeout(function (theseArgs) { doThatOtherThing(theseArgs); }, 1000);
alert('hello world');
}
votre alerte se déclenchera avant que la fonction que vous avez passée ne se déclenche.
La différence étant que l'alerte a bloqué le fil, mais votre rappel n' pas.
pour rendre votre boucle non-bloquante, vous devez la découper en sections et permettre à la boucle de traitement d'événements JS de consommer les événements Utilisateur avant de passer à la section suivante.
la façon la plus simple d'y parvenir est de faire un certain travail, puis d'utiliser setTimeout(..., 0)
pour faire la file d'attente du prochain morceau de travail. Ce qui est crucial, c'est que la file d'attente permet à la boucle d'événements JS de traiter tous les événements qui ont été mis en file d'attente entre-temps avant de passer à l'étape suivante du travail.:
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);
}
})();
}
avec usage:
yieldingLoop(1000000, 1000, function(i) {
// use i here
}, function() {
// loop done here
});
Voir http://jsfiddle.net/alnitak/x3bwjjo6/ pour une démo où le callback
fonction définit une variable à l'itération courante de comptage, et un autre setTimeout
boucles interroge la valeur courante de cette variable et les mises à jour de la page avec sa valeur.
vous ne pouvez pas exécuter deux boucles en même temps, rappelez-vous que JS est un fil simple.
ainsi, faire cela ne marchera jamais
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)
si vous voulez réaliser le multi thread vous devez utiliser les travailleurs Web , mais ils doivent avoir un fichier JS séparé et vous ne pouvez passer des objets à eux.
mais, j'ai réussi à utiliser des travailleurs Web sans les fichiers séparés en générant des fichiers Blob et je peux leur passer des fonctions de callback aussi.
//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;
}
}
Il y a en général deux façons de le faire autant que je sache. L'une est d'utiliser setTimeout
(ou requestAnimationFrame
si vous faites cela dans un environnement de support). @Alnitak a montré comment faire cela dans une autre réponse. Une autre façon est d'utiliser un travailleur web pour terminer votre logique de blocage dans un thread séparé, de sorte que le thread principal de L'interface utilisateur n'est pas bloqué.
par requestAnimationFrame
ou 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();
}
à l'Aide de web travailleur:
/***** 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
});
}
vous ne pouvez pas exécuter la solution de travailleur web car elle nécessite un fichier worker.js
séparé pour la logique de travailleur hôte.
si vous utilisez jQuery, j'ai créé une implémentation différée de réponse D'Alnitak
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();
}
alors vous pourrez utiliser la promesse retournée pour gérer la progression et le rappel fait:
var testArray =["Banana", "Orange", "Apple", "Mango"];
deferredEach(testArray, 2).progress(function(index, item){
alert(item);
}).done(function(){
alert("Done!");
})