JavaScript, Node.js: czy Array.forEach jest asynchroniczny?

Odpowiedzi:

392

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 .

Felix Kling
źródło
37
Jeśli używasz Node.js, rozważ również użycie process.nextTick zamiast setTimeout
Marcello Bastea-Forte
28
technicznie rzecz biorąc, forEach nie „blokuje”, ponieważ procesor nigdy nie idzie spać. Jest synchroniczny i związany z procesorem, co może sprawiać wrażenie „blokowania”, gdy oczekujesz, że aplikacja węzła będzie reagować na zdarzenia.
Dave Dopson
3
async byłby prawdopodobnie bardziej odpowiednim rozwiązaniem (w rzeczywistości widziałem, że ktoś opublikował to jako odpowiedź!).
James
6
Ufałem tej odpowiedzi, ale w niektórych przypadkach wydaje się ona błędna. forEachma nie blokować na awaitsprawozdaniach za przykład i należy raczej używać forpętli: stackoverflow.com/questions/37962880/...
Richard
3
@Richard: oczywiście. Możesz używać tylko funkcji awaitwewnętrznych async. Ale forEachnie wie, jakie są funkcje asynchroniczne. Należy pamiętać, że funkcje asynchroniczne to tylko funkcje zwracające obietnicę. Czy spodziewałbyś się, że forEachspełnisz obietnicę zwróconą z wywołania zwrotnego? forEachcał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.
Felix Kling
80

Jeśli potrzebujesz wersji asynchronicznej Array.forEachi 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
});
Caolan
źródło
2
Jeśli musisz upewnić się, że operacja asynchroniczna jest uruchamiana tylko dla jednego elementu na raz (w kolejności kolekcji) , musisz użyć eachSerieszamiast tego.
matpop
@JohnKennedy Widziałem cię już wcześniej!
Xsmael,
16

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 ();
}
Dave Dopson
źródło
11
Żeby było jasne ... Węzeł nie jest jednowątkowy, ale wykonanie JavaScript jest. IO i co nie działa na osobnych wątkach.
Brad
3
@Brad - może. to zależy od implementacji. Przy odpowiedniej obsłudze jądra interfejs między Węzłem a jądrem może być oparty na zdarzeniach - kqueue (mac), epoll (linux), porty zakończenia We / Wy (Windows). Jako rezerwę działa również pula wątków. Twój podstawowy punkt jest jednak słuszny. Implementacja węzła niskiego poziomu może mieć wiele wątków. Ale NIGDY nie będą bezpośrednio narażać ich na obszar użytkownika JS, ponieważ złamałoby to cały model językowy.
Dave Dopson
4
Zgadzam się, wyjaśniam tylko, ponieważ wiele osób pomyliło to pojęcie.
Brad
6

Array.forEachjest 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.

Tobu
źródło
5

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;
    });
};
Josh Mc
źródło
1

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);
};
Rax Wunter
źródło
Jak to jest asynchroniczne? AFAIK #call wykona się natychmiast?
Giles Williams,
1
Oczywiście natychmiast, ale masz funkcję zwrotną, aby wiedzieć, kiedy wszystkie iteracje zostaną zakończone. Tutaj argument „iterator” jest funkcją asynchroniczną typu węzła z wywołaniem zwrotnym. Jest podobny do metody asynchronicznej
Rax Wunter
3
Nie rozumiem, jak to jest asynchroniczne. zadzwoń lub zastosuj są synchroniczne.
Oddzwonienie
w javascript, gdy ludzie mówią asynchronicznie, oznaczają, że wykonanie kodu nie blokuje głównej pętli zdarzeń (inaczej nie blokuje procesu w jednym wierszu kodu). samo umieszczenie wywołania zwrotnego nie powoduje asynchronizacji kodu, musi wykorzystywać jakąś formę zwalniania pętli zdarzeń, taką jak setTimeout lub setInterval. od czasu oczekiwania na nie, inny kod może działać bez zakłóceń.
vasilevich
0

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

Philip Kirkbride
źródło
0

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

signo
źródło
0

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
adiian
źródło
Stanie się tak, nawet jeśli napiszesz async.foreach lub inną metodę równoległą. Ponieważ jak dla pętli nie jest procesem IO, Nodejs zawsze będzie to robić synchronicznie.
Sudhanshu Gaur
-2

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");
});
Igor Litwinowicz
źródło