J'ai une question concernant l'implémentation native Array.forEach
de JavaScript: se comporte-t-il de manière asynchrone? Par exemple, si j'appelle:
[many many elements].forEach(function () {lots of work to do})
Est-ce que ce sera non bloquant?
Non, c'est bloquant. Regardez le spécification de l'algorithme .
Cependant, une implémentation peut-être plus facile à comprendre est donnée sur MDN :
if (!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun /*, thisp */)
{
"use strict";
if (this === void 0 || this === null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in t)
fun.call(thisp, t[i], i, t);
}
};
}
Si vous devez exécuter beaucoup de code pour chaque élément, vous devriez envisager une approche différente:
function processArray(items, process) {
var todo = items.concat();
setTimeout(function() {
process(todo.shift());
if(todo.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}
puis appelez-le avec:
processArray([many many elements], function () {lots of work to do});
Ce serait alors non bloquant. L'exemple est tiré de JavaScript haute performance .
Une autre option pourrait être travailleurs Web .
Si vous avez besoin d'une version conviviale asynchrone de Array.forEach
et similaire, ils sont disponibles dans le module 'async' de Node.js: http://github.com/caolan/async . .. en bonus, ce module fonctionne également dans le navigateur.
async.each(openFiles, saveFile, function(err){
// if any of the saves produced an error, err would equal that error
});
Il existe un modèle commun pour effectuer un calcul très lourd dans Node qui peut être applicable à vous ...
Le noeud est à thread unique (en tant que choix de conception délibéré, voir Qu'est-ce que Node.js? ); cela signifie qu'il ne peut utiliser qu'un seul noyau. Les boîtes modernes ont 8, 16 ou même plus de noyaux, ce qui pourrait laisser plus de 90% de la machine inactive. Le modèle habituel pour un service REST consiste à lancer un processus de noeud par cœur et à les placer derrière un équilibreur de charge local tel que http://nginx.org/ .
Déchiffrer un enfant - Pour ce que vous essayez de faire, il existe un autre schéma courant: l'abandon un processus enfantin pour faire le gros du travail. L'avantage, c'est que le processus enfant peut effectuer des calculs lourds en arrière-plan, alors que votre processus parent est réactif à d'autres événements. Le problème est que vous ne pouvez pas/ne devriez pas partager de mémoire avec ce processus enfant (pas sans BEAUCOUP de contorsions et du code natif); vous devez passer des messages. Cela fonctionnera à merveille si la taille de vos données d'entrée et de sortie est petite comparée au calcul à effectuer. Vous pouvez même lancer un processus enfant node.js et utiliser le même code que celui que vous utilisiez précédemment.
Par exemple:
var processus_enfant = require ('processus_enfant'); fonction run_in_child (array, cb) { processus = child_process.exec ('node libfn.js', function (err , stdout, stderr) { var output = JSON.parse (stdout); cb (err, output); }); process.stdin. write (JSON.stringify (array), 'utf8'); process.stdin.end (); }
Array.forEach
est destiné aux ordinateurs qui n'attendent pas, et il n'y a rien à gagner à rendre les calculs asynchrones dans une boucle d'événement (les webworkers ajoutent le multitraitement, si vous avez besoin d'un calcul multi-cœur). Si vous souhaitez attendre la fin de plusieurs tâches, utilisez un compteur que vous pouvez envelopper dans une classe de sémaphores.
Edit 2018-10-11: Il semble y avoir de bonnes chances que la norme décrite ci-dessous ne passe pas, envisagez pipeline comme alternative (ne se comporte pas exactement de la même façon, mais des méthodes pourraient être mises en œuvre de manoir similaire).
C'est exactement pourquoi je suis enthousiasmé par es7. À l'avenir, vous pourrez faire quelque chose comme le code ci-dessous (certaines spécifications ne sont pas complètes, utilisez-les avec prudence, je vais essayer de le tenir à jour). Mais en utilisant essentiellement l'opérateur new :: bind, vous pourrez exécuter une méthode sur un objet comme si le prototype de l'objet en contenait. Par exemple, [Object] :: [Method] où normalement vous appelleriez [Object]. [ObjectsMethod]
Notez que pour faire ceci aujourd'hui (24 juillet 16) et que cela fonctionne dans tous les navigateurs, vous devrez transpiler votre code pour les fonctionnalités suivantes: Import/Export , Fonctions des flèches , Promesses , Async/Await et surtout fonction bind . Le code ci-dessous pourrait être modifié pour utiliser uniquement la fonction bind si nécessaire, toutes ces fonctionnalités sont aujourd'hui parfaitement disponibles en utilisant babel .
YourCode.js (où ' de nombreux travaux à effectuer ' doivent simplement renvoyer une promesse et la résoudre lorsque le travail asynchrone est terminé.)
import { asyncForEach } from './ArrayExtensions.js';
await [many many elements]::asyncForEach(() => lots of work to do);
ArrayExtensions.js
export function asyncForEach(callback)
{
return Promise.resolve(this).then(async (ar) =>
{
for(let i=0;i<ar.length;i++)
{
await callback.call(ar, ar[i], i, ar);
}
});
};
export function asyncMap(callback)
{
return Promise.resolve(this).then(async (ar) =>
{
const out = [];
for(let i=0;i<ar.length;i++)
{
out[i] = await callback.call(ar, ar[i], i, ar);
}
return out;
});
};
C'est une fonction asynchrone courte à utiliser sans nécessiter de bibliothèques tierces
Array.prototype.each = function (iterator, callback) {
var iterate = function () {
pointer++;
if (pointer >= this.length) {
callback();
return;
}
iterator.call(iterator, this[pointer], iterate, pointer);
}.bind(this),
pointer = -1;
iterate(this);
};
Voici un petit exemple que vous pouvez exécuter pour le tester:
[1,2,3,4,5,6,7,8,9].forEach(function(n){
var sum = 0;
console.log('Start for:' + n);
for (var i = 0; i < ( 10 - n) * 100000000; i++)
sum++;
console.log('Ended for:' + n, sum);
});
Cela produira quelque chose comme ceci (si cela prend trop moins/beaucoup de temps, augmentez/diminuez le nombre d'itérations):
(index):48 Start for:1
(index):52 Ended for:1 900000000
(index):48 Start for:2
(index):52 Ended for:2 800000000
(index):48 Start for:3
(index):52 Ended for:3 700000000
(index):48 Start for:4
(index):52 Ended for:4 600000000
(index):48 Start for:5
(index):52 Ended for:5 500000000
(index):48 Start for:6
(index):52 Ended for:6 400000000
(index):48 Start for:7
(index):52 Ended for:7 300000000
(index):48 Start for:8
(index):52 Ended for:8 200000000
(index):48 Start for:9
(index):52 Ended for:9 100000000
(index):45 [Violation] 'load' handler took 7285ms
Il existe un paquet sur npm pour easy asynchrone pour chaque boucle .
var forEachAsync = require('futures').forEachAsync;
// waits for one request to finish before beginning the next
forEachAsync(['dogs', 'cats', 'octocats'], function (next, element, index, array) {
getPics(element, next);
// then after all of the elements have been handled
// the final callback fires to let you know it's all done
}).then(function () {
console.log('All requests have finished');
});
Aussi une autre variante forAllAsync
Il est possible de coder même la solution comme celle-ci par exemple:
var loop = function(i, data, callback) {
if (i < data.length) {
//TODO("SELECT * FROM stackoverflowUsers;", function(res) {
//data[i].meta = res;
console.log(i, data[i].title);
return loop(i+1, data, errors, callback);
//});
} else {
return callback(data);
}
};
loop(0, [{"title": "hello"}, {"title": "world"}], function(data) {
console.log("DONE\n"+data);
});
Par contre, c'est beaucoup plus lent qu'un "pour".
Sinon, l'excellente bibliothèque Async peut faire cela: https://caolan.github.io/async/docs.html#each