JavaScript, Node.js: apakah Array.forEach asynchronous?

Jawaban:

392

Tidak, itu memblokir. Lihat spesifikasi algoritma .

Namun implementasi yang mungkin lebih mudah dipahami diberikan pada 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);
    }
  };
}

Jika Anda harus mengeksekusi banyak kode untuk setiap elemen, Anda harus mempertimbangkan untuk menggunakan pendekatan yang berbeda:

function processArray(items, process) {
    var todo = items.concat();

    setTimeout(function() {
        process(todo.shift());
        if(todo.length > 0) {
            setTimeout(arguments.callee, 25);
        }
    }, 25);
}

dan kemudian menyebutnya dengan:

processArray([many many elements], function () {lots of work to do});

Ini akan menjadi non-blocking. Contohnya diambil dari JavaScript Kinerja Tinggi .

Pilihan lain mungkin pekerja web .

Felix Kling
sumber
37
Jika Anda menggunakan Node.js, pertimbangkan juga untuk menggunakan process.nextTick alih-alih setTimeout
Marcello Bastea-Forte
28
secara teknis, forEach tidak "memblokir", karena CPU tidak pernah tidur. Ini sinkron dan terikat-CPU, yang bisa terasa seperti "memblokir" ketika Anda mengharapkan aplikasi node responsif terhadap peristiwa.
Dave Dopson
3
async mungkin akan menjadi solusi yang lebih tepat di sini (sebenarnya hanya melihat seseorang memposting itu sebagai jawaban!).
James
6
Saya mempercayai jawaban ini, tetapi dalam beberapa kasus sepertinya salah. forEachmisalnya, tidak memblokir awaitpernyataan dan Anda sebaiknya menggunakan forperulangan: stackoverflow.com/questions/37962880/…
Richard
3
@ Richard: tentu saja. Anda hanya dapat menggunakan fungsi awaitdi dalam async. Tetapi forEachtidak tahu apa fungsi async. Perlu diingat bahwa fungsi async hanyalah fungsi mengembalikan janji. Apakah Anda berharap forEachuntuk menangani janji yang dikembalikan dari panggilan balik? forEachsepenuhnya mengabaikan nilai balik dari callback. Itu hanya akan dapat menangani panggilan balik async jika async itu sendiri.
Felix Kling
80

Jika Anda memerlukan versi asynchronous-friendly Array.forEachdan sejenisnya, mereka tersedia di modul 'async' Node.js: http://github.com/caolan/async ... sebagai bonus modul ini juga berfungsi di browser .

async.each(openFiles, saveFile, function(err){
    // if any of the saves produced an error, err would equal that error
});
Caolan
sumber
2
Jika Anda perlu memastikan bahwa operasi async dijalankan hanya untuk satu item pada satu waktu (dalam urutan koleksi) , Anda harus menggunakannya eachSeries.
matpop
@ JohnKennedy, saya pernah melihat Anda sebelumnya!
Xsmael
16

Ada pola umum untuk melakukan perhitungan yang sangat berat di Node yang mungkin berlaku untuk Anda ...

Node adalah single-threaded (sebagai pilihan desain yang disengaja, lihat Apa itu Node.js? ); ini artinya hanya dapat menggunakan satu inti. Kotak-kotak modern memiliki 8, 16, atau bahkan lebih banyak core, sehingga ini bisa membuat 90% mesin idle. Pola umum untuk layanan REST adalah menjalankan satu proses simpul per inti, dan meletakkannya di belakang penyeimbang beban lokal seperti http://nginx.org/ .

Forking a child - Untuk apa yang Anda coba lakukan, ada pola umum lainnya, yaitu menghentikan proses seorang anak untuk melakukan pekerjaan berat. Keuntungannya adalah proses anak dapat melakukan perhitungan berat di latar belakang sementara proses orang tua Anda responsif terhadap peristiwa lain. Tangkapannya adalah bahwa Anda tidak dapat / tidak boleh berbagi memori dengan proses anak ini (bukan tanpa BANYAK contortions dan beberapa kode asli); Anda harus mengirim pesan. Ini akan bekerja dengan baik jika ukuran input dan output data Anda kecil dibandingkan dengan perhitungan yang harus dilakukan. Anda bahkan dapat menjalankan proses node.js anak dan menggunakan kode yang sama seperti yang Anda gunakan sebelumnya.

Sebagai contoh:

var child_process = butuhkan ('child_process');
function run_in_child (array, cb) {
    var process = child_process.exec ('node libfn.js', fungsi (err, stdout, stderr) {
        var output = JSON.parse (stdout);
        cb (err, output);
    });
    process.stdin.write (JSON.stringify (array), 'utf8');
    process.stdin.end ();
}
Dave Dopson
sumber
11
Untuk lebih jelasnya ... Node bukan single threaded, tetapi eksekusi JavaScript Anda adalah. IO dan apa yang tidak berjalan di utas terpisah.
Brad
3
@Brad - mungkin. itu tergantung implementasi. Dengan dukungan kernel yang tepat, antarmuka antara Node dan kernel dapat berbasis acara - kqueue (mac), epoll (linux), port penyelesaian IO (windows). Sebagai mundur, genangan benang juga berfungsi. Tapi poin dasar Anda benar. Implementasi Node tingkat rendah mungkin memiliki beberapa utas. Tetapi mereka tidak akan pernah secara langsung mengekspos mereka ke JS userland karena itu akan merusak seluruh model bahasa.
Dave Dopson
4
Benar, saya hanya mengklarifikasi karena konsepnya telah membingungkan banyak orang.
Brad
6

Array.forEachdimaksudkan untuk menghitung hal-hal yang tidak menunggu, dan tidak ada yang bisa diperoleh dengan membuat komputasi asinkron dalam suatu peristiwa (pekerja web menambahkan multi-pemrosesan, jika Anda memerlukan komputasi multi-inti). Jika Anda ingin menunggu beberapa tugas berakhir, gunakan penghitung, yang bisa Anda bungkus dalam kelas semaphore.

Tobu
sumber
5

Sunting 2018-10-11: Sepertinya ada peluang bagus standar yang dijelaskan di bawah ini mungkin tidak dapat dilalui, pertimbangkan perpipaan sebagai alternatif (tidak berperilaku sama persis tetapi metode dapat diimplementasikan di manor serupa).

Inilah tepatnya mengapa saya senang dengan es7, di masa depan Anda akan dapat melakukan sesuatu seperti kode di bawah ini (beberapa spesifikasi tidak lengkap jadi gunakan dengan hati-hati, saya akan mencoba untuk tetap up to date). Tetapi pada dasarnya menggunakan operator :: bind baru, Anda akan dapat menjalankan metode pada objek seolah-olah prototipe objek berisi metode. misalnya [Objek] :: [Metode] di mana biasanya Anda akan memanggil [Objek]. [ObjectsMethod]

Catatan untuk melakukan ini hari ini (24-Juli-16) dan membuatnya bekerja di semua browser Anda perlu mengubah kode Anda untuk fungsionalitas berikut: Impor / Ekspor , fungsi Panah , Janji , Async / Menunggu dan yang paling penting, fungsi bind . Kode di bawah ini dapat dimodifikasi untuk hanya menggunakan fungsi bind jika perlu, semua fungsi ini tersedia dengan rapi saat ini dengan menggunakan babel .

YourCode.js (di mana ' banyak pekerjaan harus dilakukan ' hanya harus mengembalikan janji, menyelesaikannya ketika pekerjaan asinkron dilakukan.)

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
sumber
1

Ini adalah fungsi asinkron pendek untuk digunakan tanpa memerlukan lib pihak ketiga

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
sumber
Bagaimana asinkron ini? AFAIK #call akan segera dieksekusi?
Giles Williams
1
Tentu saja segera, tetapi Anda memiliki fungsi panggilan balik untuk mengetahui kapan semua iterasi diselesaikan. Di sini argumen "iterator" adalah fungsi async gaya simpul dengan panggilan balik. Ini mirip dengan metode async.each
Rax Wunter
3
Saya tidak melihat bagaimana ini async. panggilan atau terapkan sinkron. Memiliki panggilan balik tidak membuatnya menjadi async
adrianvlupu
dalam javascript ketika orang mengatakan async, artinya eksekusi kode tidak memblokir loop peristiwa utama (alias, itu tidak membuat proses macet pada satu baris kode). hanya dengan melakukan panggilan balik tidak membuat kode async, ia harus menggunakan beberapa bentuk rilis acara seperti setTimeout, atau setInterval. sejak menolak waktu Anda menunggu untuk itu, kode lain dapat berjalan tanpa gangguan.
vasilevich
0

Ada paket di npm untuk asinkron mudah untuk setiap loop .

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');
});

Juga variasi lain untuk AllAsync

Philip Kirkbride
sumber
0

Dimungkinkan untuk membuat kode bahkan solusi seperti ini misalnya:

 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);
});

Di sisi lain, ini jauh lebih lambat daripada "untuk".

Jika tidak, pustaka Async yang unggul dapat melakukan ini: https://caolan.github.io/async/docs.html#each

signo
sumber
0

Ini adalah contoh kecil yang bisa Anda jalankan untuk mengujinya:

[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);
});

Ini akan menghasilkan sesuatu seperti ini (jika terlalu sedikit / banyak waktu, tambah / kurangi jumlah iterasi):

(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
sumber
Ini akan terjadi bahkan jika Anda akan menulis async.foreach atau metode paralel lainnya. Karena untuk loop bukan proses IO, Nodejs akan selalu melakukannya secara sinkron.
Sudhanshu Gaur
-2

Gunakan Promise.each dari bluebird perpustakaan.

Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise

Metode ini mengulangi array, atau janji array, yang berisi janji-janji (atau campuran dari janji-janji dan nilai-nilai) dengan fungsi iterator yang diberikan dengan tanda tangan (nilai, indeks, panjang) di mana nilai adalah nilai yang diselesaikan dari suatu janji masing-masing dalam array input. Iterasi terjadi secara serial. Jika fungsi iterator mengembalikan janji atau janji, maka hasil dari janji tersebut ditunggu sebelum melanjutkan dengan iterasi berikutnya. Jika ada janji dalam array input yang ditolak, maka janji yang dikembalikan juga ditolak.

Jika semua iterasi berhasil diselesaikan, Promise.each memutuskan ke array asli yang tidak dimodifikasi . Namun, jika satu iterasi menolak atau kesalahan, Promise.each segera menghentikan eksekusi dan tidak memproses iterasi lebih lanjut. Kesalahan atau nilai yang ditolak dikembalikan dalam kasus ini alih-alih array asli.

Metode ini dimaksudkan untuk digunakan untuk efek samping.

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 Litvinovich
sumber