JavaScript, Node.js: Array.forEach zaman uyumsuz mu?


Yanıtlar:


392

Hayır, engelliyor. Algoritmanın özelliklerine bir göz atın .

Ancak MDN'de anlaşılması belki daha kolay bir uygulamadır :

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

Her öğe için çok sayıda kod yürütmeniz gerekiyorsa, farklı bir yaklaşım kullanmayı düşünmelisiniz:

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

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

ve şununla ara:

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

O zaman bu engellemeyecekti. Örnek Yüksek Performanslı JavaScript'ten alınmıştır .

Başka bir seçenek web çalışanları olabilir .


37
Node.js kullanıyorsanız , setTimeout yerine process.nextTick kullanmayı da düşünün
Marcello Bastea-Forte

28
teknik olarak, CPU asla uykuya geçmediği için forEach "engellemez". Düğüm uygulamasının olaylara yanıt vermesini beklediğinizde "engelleme" gibi hissedebilen senkronize ve CPU'ya bağlıdır.
Dave Dopson

3
async muhtemelen burada daha uygun bir çözüm olacaktır (aslında birinin bunu cevap olarak gönderdiğini gördüm!).
James

6
Bu cevaba güvendim, ancak bazı durumlarda yanlış gibi görünüyor. forEachyok değil engelle awaitörneğin tablolar ve yerine bir kullanmalıdır fordöngü: stackoverflow.com/questions/37962880/...
Richard

3
@Richard: tabii ki. Yalnızca awaitasyncişlevleri kullanabilirsiniz . Ancak forEachasenkron fonksiyonların ne olduğunu bilmiyor. Zaman uyumsuz işlevlerin yalnızca bir söz veren işlevler olduğunu unutmayın. forEachGeri aramadan iade edilen bir sözü yerine getirmeyi bekler misiniz ? forEachGeri aramadaki dönüş değerini tamamen yok sayar. Bir eşzamansız geri çağrıyı yalnızca eşzamansız kendisi olsaydı işleyebilirdi.
Felix Kling

80

Eşzamanlı olmayan bir sürümüne Array.forEachve benzeri bir sürüme ihtiyacınız varsa , Node.js 'eşzamansız' modülünde mevcuttur: http://github.com/caolan/async ... bonus olarak bu modül tarayıcıda da çalışır .

async.each(openFiles, saveFile, function(err){
    // if any of the saves produced an error, err would equal that error
});

2
Eşzamansız işlemenin bir seferde yalnızca bir öğe için (koleksiyon sırasında) çalıştırıldığından emin olmanız gerekiyorsa, eachSeriesbunun yerine kullanmanız gerekir .
matpop

@JohnKennedy Seni daha önce gördüm!
Xsmael

16

Düğümde sizin için geçerli olabilecek gerçekten ağır bir hesaplama yapmak için yaygın bir kalıp vardır ...

Düğüm tek iş parçacıklı (kasıtlı bir tasarım seçeneği olarak bkz. Node.js nedir? ); bu sadece tek bir çekirdeği kullanabileceği anlamına gelir. Modern kutular 8, 16 veya daha fazla çekirdeğe sahiptir, bu da makinenin% 90 + 'sını boşta bırakabilir. Bir REST hizmeti için ortak model, çekirdek başına bir düğüm işlemi başlatmak ve bunları http://nginx.org/ gibi yerel bir yük dengeleyicisinin arkasına koymaktır .

Bir çocuğu çatallamak - Yapmaya çalıştığınız şey için, ağır kaldırmayı yapmak için bir çocuk sürecini çürüten başka bir ortak desen var. Bunun üst tarafı, ana süreciniz diğer olaylara yanıt verirken alt sürecin arka planda ağır hesaplama yapabilmesidir. Yakalama, bu alt süreçle hafızayı paylaşamayacağınız / paylaşamamanızdır (çok fazla eğri ve bazı yerel kodlar olmadan değil); mesajları iletmeniz gerekiyor. Giriş ve çıkış verilerinizin boyutu, gerçekleştirilmesi gereken hesaplamaya kıyasla küçükse, güzel bir şekilde çalışacaktır. Bir alt node.js işlemini bile başlatabilir ve daha önce kullandığınız kodun aynısını kullanabilirsiniz.

Örneğin:

var child_process = requir ('child_process');
işlev run_in_child (dizi, cb) {
    var process = child_process.exec ('düğüm libfn.js', işlev (err, stdout, stderr) {
        var output = JSON.parse (stdout);
        cb (hata, çıktı);
    });
    process.stdin.write (JSON.stringify (dizi), 'utf8');
    ) (Process.stdin.end;
}

11
Açık olmak gerekirse ... Düğüm tek iş parçacıklı değil, JavaScript'inizin yürütülmesi. IO ve ayrı iş parçacığı üzerinde çalışmayan ne.
Brad

3
@Brad - belki. bu uygulamaya bağlıdır. Uygun çekirdek desteği ile, Düğüm ve çekirdek arasındaki arabirim olay tabanlı olabilir - kqueue (mac), epoll (linux), IO tamamlama bağlantı noktaları (windows). Bir yedek olarak, bir iş parçacığı havuzu da çalışır. Temel noktanız doğru. Düşük düzeyli Düğüm uygulamasında birden çok iş parçacığı olabilir. Ancak hiçbir zaman onları doğrudan JS kullanıcı alanına maruz bırakmayacaklar, çünkü bu tüm dil modelini kıracaktır.
Dave Dopson

4
Doğru, sadece açıklığa kavuştum çünkü konsept birçok kişinin kafasını karıştırdı.
Brad

6

Array.forEachbeklemeyen şeyleri hesaplamak içindir ve bir olay döngüsünde hesaplamaları eşzamansız hale getirmek için kazanılacak hiçbir şey yoktur (çok çekirdekli hesaplamaya ihtiyacınız varsa web işçileri çoklu işlem ekler). Birden fazla görevin bitmesini beklemek istiyorsanız, bir semafor sınıfına sarılabileceğiniz bir sayaç kullanın.


5

Edit 2018-10-11: Aşağıda açıklanan standardın geçemeyebileceği iyi bir şans var gibi görünüyor, boru hattını alternatif olarak düşünün (tam olarak aynı davranmıyor, ancak benzer bir malikanede yöntemler uygulanabilir).

Bu yüzden es7 hakkında heyecanlıyım, gelecekte aşağıdaki kod gibi bir şey yapabileceksiniz (bazı özellikler tam değildir, bu yüzden dikkatli kullanın, bunu güncel tutmaya çalışacağım). Ancak temel olarak new :: bind işlecini kullanarak, nesnenin prototipi yöntemi içeriyormuş gibi bir nesne üzerinde yöntem çalıştırabilirsiniz. örneğin [Object] :: [Yöntem] burada normalde [Object] olarak adlandırırsınız. [ObjectsMethod]

Bunu bugün (24-Temmuz-16) yapmak ve tüm tarayıcılarda çalışmasını sağlamak için aşağıdaki işlevler için kodunuzu aktarmanız gerekir: İçe / Dışa Aktar , Ok işlevleri , Vaatler , Async / Bekle ve en önemlisi işlev bağlama . Aşağıdaki kod, yalnızca gerekli olduğunda bağlama işlevini kullanacak şekilde değiştirilebilir, tüm bu işlevler bugün babel kullanılarak düzgün bir şekilde kullanılabilir .

YourCode.js ('yapılacak çok iş ' basitçe bir söz vermeli ve eşzamansız iş yapıldığında çözülmelidir.)

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

1

Bu, üçüncü taraf kütüphanelerine ihtiyaç duymadan kullanılacak kısa bir asenkron işlevdir

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

Bu nasıl eşzamansız? AFAIK #call hemen yürütülecek mi?
Giles Williams

1
Tabii ki, ancak tüm tekrarların ne zaman tamamlandığını bilmek için geri arama işleviniz var. Burada "iterator" argümanı geri çağırma işlevine sahip bir düğüm stili eşzamansız işlevidir. Async.each yöntemine benzer
Rax Wunter

3
Bunun nasıl zaman uyumsuz olduğunu anlamıyorum. çağrı veya uygulama eşzamanlıdır. Geri arama olması zaman uyumsuz yapmaz
adrianvlupu

insanlar asenkron dediğinde, kod yürütmenin ana olay döngüsünü engellemediği anlamına gelir (diğer bir deyişle, işlemi bir kod satırına yapıştıramaz). yalnızca geri arama yapmak kodu zaman uyumsuz hale getirmez, setTimeout veya setInterval gibi bir olay döngüsü bırakma biçimini kullanmalıdır. bunlar için beklediğiniz süre boyunca, diğer kodlar kesintisiz çalışabilir.
vasilevich

0

Her döngü için kolay eşzamansız npm'de bir paket var .

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

AllAsync için başka bir varyasyon


0

Böyle bir çözümü bile kodlamak mümkündür, örneğin:

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

Öte yandan, bir "for" dan çok daha yavaştır.

Aksi takdirde, mükemmel Async kütüphanesi bunu yapabilir: https://caolan.github.io/async/docs.html#each


0

Test etmek için çalıştırabileceğiniz küçük bir örnek:

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

Böyle bir şey üretecektir (çok daha az / çok zaman alırsa, yineleme sayısını artırın / azaltın):

(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

Bu, async.foreach veya başka bir paralel yöntem yazsanız bile gerçekleşir. Çünkü döngü için bir IO işlemi değil Nodejs her zaman senkronize olarak yapacaktır.
Sudhanshu Gaur

-2

Kullanım Promise.each ait bluebird kütüphanesine.

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

Verilen bir dizi ya da söz içeren bir dizi (ya da söz ve değerlerin bir karışımı) bir söz üzerinde Bu yöntem dolaşır yineleyici imza ile fonksiyonu (değer, dizin, uzunluk) değeri a'nın çözülmesi değerdir giriş dizisindeki ilgili vaat. Yineleme seri olarak gerçekleşir. Yineleyici işlevi bir söz veya ötelenebilir değer döndürürse, söz konusu sonucun bir sonraki yinelemeye devam etmeden önce beklenmesi gerekir. Girdi dizisindeki herhangi bir vaat reddedilirse, döndürülen vaat de reddedilir.

Tüm yinelemeler başarıyla çözülürse , Promise.each değiştirilmemiş orijinal diziye gider . Ancak, bir yineleme reddedilirse veya hata verirse , Promise.each hemen yürütmeyi durdurur ve başka yinelemeleri işlemez. Bu durumda, orijinal dizi yerine hata veya reddedilen değer döndürülür.

Bu yöntemin yan etkiler için kullanılması amaçlanmıştır.

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");
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.