Mam pytanie dotyczące natywnej Array.forEach
implementacji JavaScript: czy zachowuje się asynchronicznie? Na przykład, jeśli zadzwonię:
[many many elements].forEach(function () {lots of work to do})
Czy to nie będzie blokować?
Mam pytanie dotyczące natywnej Array.forEach
implementacji JavaScript: czy zachowuje się asynchronicznie? Na przykład, jeśli zadzwonię:
[many many elements].forEach(function () {lots of work to do})
Czy to nie będzie blokować?
Odpowiedzi:
Nie, to blokuje. Zobacz specyfikację algorytmu .
Jednak w MDN podano łatwiejsze do zrozumienia wdrożenie :
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);
}
};
}
Jeśli musisz wykonać dużo kodu dla każdego elementu, powinieneś rozważyć zastosowanie innego podejścia:
function processArray(items, process) {
var todo = items.concat();
setTimeout(function() {
process(todo.shift());
if(todo.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}
a następnie zadzwoń za pomocą:
processArray([many many elements], function () {lots of work to do});
To nie byłoby wtedy blokowaniem. Przykład pochodzi z wysokiej wydajności JavaScript .
Inną opcją mogą być pracownicy sieci .
forEach
ma nie blokować na await
sprawozdaniach za przykład i należy raczej używać for
pętli: stackoverflow.com/questions/37962880/...
await
wewnętrznych async
. Ale forEach
nie wie, jakie są funkcje asynchroniczne. Należy pamiętać, że funkcje asynchroniczne to tylko funkcje zwracające obietnicę. Czy spodziewałbyś się, że forEach
spełnisz obietnicę zwróconą z wywołania zwrotnego? forEach
całkowicie ignoruje wartość zwrotną z wywołania zwrotnego. Byłby w stanie obsłużyć wywołanie zwrotne asynchroniczne tylko wtedy, gdyby sam był asynchroniczny.
Jeśli potrzebujesz wersji asynchronicznej Array.forEach
i podobnej, są one dostępne w module „async” Node.js: http://github.com/caolan/async ... jako bonus ten moduł działa również w przeglądarce .
async.each(openFiles, saveFile, function(err){
// if any of the saves produced an error, err would equal that error
});
eachSeries
zamiast tego.
Istnieje częsty wzorzec wykonywania bardzo ciężkich obliczeń w węźle, który może dotyczyć ciebie ...
Węzeł jest jednowątkowy (jako celowy wybór projektu zobacz Co to jest Node.js? ); oznacza to, że może wykorzystywać tylko jeden rdzeń. Nowoczesne urządzenia mają 8, 16 lub nawet więcej rdzeni, więc może to pozostawić 90% maszyny w stanie bezczynności. Typowym wzorcem dla usługi REST jest odpalanie jednego procesu węzła na rdzeń i umieszczanie ich za lokalnym modułem równoważenia obciążenia, takim jak http://nginx.org/ .
Rozwidlając dziecko - w przypadku tego, co próbujesz zrobić, istnieje inny wspólny wzór, odrywając proces dziecka od ciężkiego podnoszenia. Plusem jest to, że proces potomny może wykonywać ciężkie obliczenia w tle, podczas gdy proces nadrzędny reaguje na inne zdarzenia. Problem polega na tym, że nie możesz / nie powinieneś współdzielić pamięci z tym procesem potomnym (nie bez DUŻYCH zniekształceń i trochę natywnego kodu); musisz przekazywać wiadomości. Będzie to działało pięknie, jeśli rozmiar danych wejściowych i wyjściowych jest niewielki w porównaniu do obliczeń, które należy wykonać. Możesz nawet uruchomić potomny proces node.js i użyć tego samego kodu, którego używałeś wcześniej.
Na przykład:
var child_process = wymagany ('child_process'); funkcja run_in_child (tablica, cb) { var process = child_process.exec ('node libfn.js', function (err, stdout, stderr) { var output = JSON.parse (standardowe wyjście); cb (err, wyjście); }); process.stdin.write (JSON.stringify (tablica), 'utf8'); process.stdin.end (); }
Array.forEach
jest przeznaczony do obliczania rzeczy, które nie czekają, i nie ma nic do zyskania, czyniąc obliczenia asynchroniczne w pętli zdarzeń (pracownicy sieci dodają przetwarzanie wieloprocesowe, jeśli potrzebujesz obliczeń wielordzeniowych). Jeśli chcesz poczekać na zakończenie wielu zadań, użyj licznika, który możesz owinąć w klasę semaforów.
Edytuj 2018-10-11: Wygląda na to, że istnieje spora szansa, że opisany poniżej standard może nie przejść, rozważ potokowanie jako alternatywę (nie zachowuje się dokładnie tak samo, ale metody można zaimplementować w podobnej rezydencji).
Właśnie dlatego jestem podekscytowany es7, w przyszłości będziesz mógł zrobić coś takiego jak poniższy kod (niektóre specyfikacje nie są kompletne, więc używaj ostrożnie, postaram się to aktualizować). Ale w zasadzie używając nowego operatora :: bind, będziesz mógł uruchomić metodę na obiekcie tak, jakby prototyp obiektu zawierał tę metodę. np. [Obiekt] :: [Metoda], gdzie normalnie wywołujesz [Obiekt]. [ObjectsMethod]
Pamiętaj, aby zrobić to dzisiaj (24 lipca-16 lipca) i sprawić, aby działał we wszystkich przeglądarkach, musisz transponować kod, aby uzyskać następujące funkcje: Import / Eksport , Funkcje strzałek , Obietnice , Async / Oczekiwanie i najważniejsze powiązanie funkcji . Poniższy kod można zmodyfikować tak, aby używał tylko wiązania funkcji, jeśli jest to konieczne, cała ta funkcjonalność jest dziś dostępna za pomocą babel .
YourCode.js (gdzie „ dużo pracy do wykonania ” musi po prostu zwrócić obietnicę, rozwiązując ją po zakończeniu pracy asynchronicznej).
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;
});
};
Jest to krótka funkcja asynchroniczna, z której można korzystać bez konieczności korzystania z bibliotek stron trzecich
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);
};
Istnieje pakiet na npm dla łatwego asynchronicznego dla każdej pętli .
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');
});
Kolejna odmiana dla AllAsync
Możliwe jest kodowanie nawet takiego rozwiązania, na przykład:
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);
});
Z drugiej strony jest znacznie wolniejszy niż „za”.
W przeciwnym razie doskonała biblioteka Async może to zrobić: https://caolan.github.io/async/docs.html#each
Oto mały przykład, który możesz uruchomić, aby go przetestować:
[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);
});
Wytworzy coś takiego (jeśli zajmie to za dużo / dużo czasu, zwiększ / zmniejsz liczbę iteracji):
(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
Zastosowanie Promise.each z Bluebird bibliotece.
Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise
Ta metoda iteruje tablicę lub obietnicę tablicy, która zawiera obietnice (lub kombinację obietnic i wartości) z daną funkcją iteratora z podpisem (wartość, indeks, długość), gdzie wartość jest wartością rozstrzygniętą odpowiednia obietnica w tablicy wejściowej. Iteracja odbywa się szeregowo.Jeśli funkcja iteratora zwraca obietnicę lub niemożliwą, wówczas wynik obietnicy jest oczekiwany przed kontynuowaniem następnej iteracji. Jeśli jakakolwiek obietnica w tablicy wejściowej zostanie odrzucona, wówczas również zwrócona obietnica zostanie odrzucona.
Jeśli wszystkie iteracje zakończą się powodzeniem, Promise.each rozpoznaje pierwotną tablicę niezmodyfikowaną . Jeśli jednak jedna iteracja odrzuci lub popełni błąd, Promise.each natychmiast przerywa wykonywanie i nie przetwarza dalszych iteracji. Błąd lub odrzucona wartość jest w tym przypadku zwracana zamiast oryginalnej tablicy.
Ta metoda jest przeznaczona do stosowania w przypadku działań niepożądanych.
var fileNames = ["1.txt", "2.txt", "3.txt"];
Promise.each(fileNames, function(fileName) {
return fs.readFileAsync(fileName).then(function(val){
// do stuff with 'val' here.
});
}).then(function() {
console.log("done");
});