Node JS Promise.all ve forEach


121

Zaman uyumsuz yöntemleri ortaya çıkaran dizi benzeri bir yapıya sahibim. Zaman uyumsuz yöntem, sırayla daha fazla zaman uyumsuz yöntem ortaya çıkaran dizi yapılarını döndürür. Bu yapıdan elde edilen değerleri saklamak için başka bir JSON nesnesi oluşturuyorum ve bu nedenle geri aramalardaki referansları takip etme konusunda dikkatli olmam gerekiyor.

Bir kaba kuvvet çözümü kodladım, ancak daha deyimsel veya temiz bir çözüm öğrenmek istiyorum.

  1. Model n seviyeli iç içe geçme için tekrarlanabilir olmalıdır.
  2. Kapsamdaki rutini ne zaman çözeceğimi belirlemek için promise.all veya benzer bir teknik kullanmam gerekiyor.
  3. Her öğe, eşzamansız bir çağrı yapmayı gerektirmez. Yani iç içe geçmiş bir promise.all'da JSON dizi öğelerime dizine dayalı olarak atamalar yapamıyorum. Bununla birlikte, tüm özellik atamalarının, çevreleyen rutini çözmeden önce yapıldığından emin olmak için yuvalanmış forEach'de promise.all gibi bir şey kullanmam gerekiyor.
  4. Bluebird promise lib kullanıyorum ama bu bir gereklilik değil

İşte bazı kısmi kodlar -

var jsonItems = [];

items.forEach(function(item){

  var jsonItem = {};
  jsonItem.name = item.name;
  item.getThings().then(function(things){
  // or Promise.all(allItemGetThingCalls, function(things){

    things.forEach(function(thing, index){

      jsonItems[index].thingName = thing.name;
      if(thing.type === 'file'){

        thing.getFile().then(function(file){ //or promise.all?

          jsonItems[index].filesize = file.getSize();

Bu, geliştirmek istediğim çalışma kaynağına bağlantıdır. github.com/pebanfield/change-view-service/blob/master/src/…
user3205931

1
Örnekte bluebird kullandığınızı görüyorum, bluebird bu durumda (eşzamanlı) ve (sıralı) ile hayatınızı daha da kolaylaştırıyor , ayrıca not kullanımdan kaldırıldı - cevabımdaki kod vaatleri geri vererek bundan nasıl kaçınılacağını gösteriyor . Sözler tamamen dönüş değerleri ile ilgilidir. Promise.mapPromise.eachPromise.defer
Benjamin Gruenbaum

Yanıtlar:


369

Bazı basit kurallarla oldukça basit:

  • Ne zaman bir söz oluştursan then, onu geri ver - hiçbir söz dışarıda beklemeyecektir.
  • Ne zaman birden fazla söz yaratsan, .allbunlar - bu şekilde tüm vaatleri bekler ve hiçbirinden gelen hiçbir hata susturulmaz.
  • Ne zaman iç içe thengeçmişseniz, genellikle ortasına geri dönebilirsiniz -then zincirlere zincirler genellikle en fazla 1 seviye derinliğindedir.
  • Ne zaman bir IO gerçekleştirirseniz, bir söz vermelidir - ya bir söz vermeli ya da tamamlandığını belirtmek için bir söz kullanmalıdır.

Ve bazı ipuçları:

  • Haritalama iyi ile yapılır .mapile dahafor/push - Bir fonksiyonu ile eğer sen haritalama değerlerimap eşliyorsanız, eylemleri tek tek uygulama ve sonuçları toplama kavramını kısaca ifade etmenize olanak tanır.
  • Eşzamanlılık, eğer ücretsizse sıralı yürütmeden daha iyidir - işleri eşzamanlı olarak yürütmek ve onları Promise.allbirbiri ardına yürütmektense beklemek daha iyidir - her biri diğerinden önce bekler.

Tamam, hadi başlayalım:

var items = [1, 2, 3, 4, 5];
var fn = function asyncMultiplyBy2(v){ // sample async action
    return new Promise(resolve => setTimeout(() => resolve(v * 2), 100));
};
// map over forEach since it returns

var actions = items.map(fn); // run the function over all items

// we now have a promises array and we want to wait for it

var results = Promise.all(actions); // pass array of promises

results.then(data => // or just .then(console.log)
    console.log(data) // [2, 4, 6, 8, 10]
);

// we can nest this of course, as I said, `then` chains:

var res2 = Promise.all([1, 2, 3, 4, 5].map(fn)).then(
    data => Promise.all(data.map(fn))
).then(function(data){
    // the next `then` is executed after the promise has returned from the previous
    // `then` fulfilled, in this case it's an aggregate promise because of 
    // the `.all` 
    return Promise.all(data.map(fn));
}).then(function(data){
    // just for good measure
    return Promise.all(data.map(fn));
});

// now to get the results:

res2.then(function(data){
    console.log(data); // [16, 32, 48, 64, 80]
});

5
Ah, sizin bakış açınızdan bazı kurallar :-)
Bergi

1
@Bergi birileri gerçekten bu kuralların bir listesini ve sözler hakkında kısa bir arka plan yapmalı. Muhtemelen bluebirdjs.com'da barındırabiliriz.
Benjamin Gruenbaum

Sadece teşekkür etmem gerektiği için - bu örnek iyi görünüyor ve harita önerisini beğendim, ancak yalnızca bazılarının zaman uyumsuz yöntemlere sahip olduğu bir nesne koleksiyonu için ne yapmalı? (Yukarıdaki 3 noktam) Her öğe için ayrıştırma mantığını bir işleve soyutlayacağım ve daha sonra eşzamansız çağrı yanıtında ya da hiçbir zaman uyumsuz çağrının olmadığı yerde çözümlenmesini sağlayacağıma dair bir fikrim vardı. bu mantıklı mı?
user3205931

Ayrıca, harita işlevinin hem oluşturduğum json nesnesini hem de eşzamansız çağrının sonucunu döndürmesini sağlamalıyım, bunu da nasıl yapacağımdan emin olmam gerekiyor - sonunda bir dizinde yürüdüğüm için her şeyin özyinelemeli olması gerekiyor yapı - Hala bunu çiğniyorum ama ücretli işler yoluma çıkıyor :(
user3205931

2
@ user3205931 vaatleri kolay değil basittir , yani diğer şeyler kadar tanıdık gelmezler ama onları bir kez salladığınızda kullanmak çok daha iyidir. Sıkı
durun, anlayacaksınız

42

Azaltmanın basit bir örneğini burada bulabilirsiniz. Seri olarak çalışır, ekleme sırasını korur ve Bluebird gerektirmez.

/**
 * 
 * @param items An array of items.
 * @param fn A function that accepts an item from the array and returns a promise.
 * @returns {Promise}
 */
function forEachPromise(items, fn) {
    return items.reduce(function (promise, item) {
        return promise.then(function () {
            return fn(item);
        });
    }, Promise.resolve());
}

Ve bunu şu şekilde kullanın:

var items = ['a', 'b', 'c'];

function logItem(item) {
    return new Promise((resolve, reject) => {
        process.nextTick(() => {
            console.log(item);
            resolve();
        })
    });
}

forEachPromise(items, logItem).then(() => {
    console.log('done');
});

Döngüye isteğe bağlı bir bağlam göndermeyi faydalı bulduk. Bağlam isteğe bağlıdır ve tüm yinelemeler tarafından paylaşılır.

function forEachPromise(items, fn, context) {
    return items.reduce(function (promise, item) {
        return promise.then(function () {
            return fn(item, context);
        });
    }, Promise.resolve());
}

Söz fonksiyonunuz şöyle görünecektir:

function logItem(item, context) {
    return new Promise((resolve, reject) => {
        process.nextTick(() => {
            console.log(item);
            context.itemCount++;
            resolve();
        })
    });
}

Bunun için teşekkürler - çözümünüz, diğerlerinin (çeşitli npm kütüphaneleri dahil) çalışmadığı yerlerde benim için çalıştı. Bunu npm'de yayınladınız mı?
SamF

Teşekkür ederim. İşlev, tüm Sözlerin çözüldüğünü varsayar. Reddedilen sözlerle nasıl başa çıkacağız? Ayrıca, bir değeri olan başarılı vaatleri nasıl yerine getiririz?
oyalhi

@oyalhi 'bağlamı' kullanmanızı ve hatayla eşleştirilmiş bir dizi reddedilen girdi parametresi eklemenizi öneririm. Bazıları geri kalan tüm vaatleri görmezden gelmek isteyeceği ve bazıları istemeyeceği için bu gerçekten kullanım durumu başına. Dönen değer için benzer bir yaklaşım da kullanabilirsiniz.
Steven Spungin

1

Ben de aynı durumu yaşadım. İki Promise.All () kullanarak çözdüm.

Gerçekten iyi bir çözüm olduğunu düşünüyorum, bu yüzden npm'de yayınladım: https://www.npmjs.com/package/promise-foreach

Sanırım kodunuz böyle bir şey olacak

var promiseForeach = require('promise-foreach')
var jsonItems = [];
promiseForeach.each(jsonItems,
    [function (jsonItems){
        return new Promise(function(resolve, reject){
            if(jsonItems.type === 'file'){
                jsonItems.getFile().then(function(file){ //or promise.all?
                    resolve(file.getSize())
                })
            }
        })
    }],
    function (result, current) {
        return {
            type: current.type,
            size: jsonItems.result[0]
        }
    },
    function (err, newList) {
        if (err) {
            console.error(err)
            return;
        }
        console.log('new jsonItems : ', newList)
    })

0

Sadece sunulan çözüme eklemek için, benim durumumda bir ürün listesi için Firebase'den birden fazla veri almak istedim. İşte böyle yaptım:

useEffect(() => {
  const fn = p => firebase.firestore().doc(`products/${p.id}`).get();
  const actions = data.occasion.products.map(fn);
  const results = Promise.all(actions);
  results.then(data => {
    const newProducts = [];
    data.forEach(p => {
      newProducts.push({ id: p.id, ...p.data() });
    });
    setProducts(newProducts);
  });
}, [data]);
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.