Node.js yerel Promise.all işleme paralel mi yoksa ardışık mı?


174

Dokümantasyon konusunda çok açık olmadığı için bu noktayı açıklığa kavuşturmak istiyorum ;

S1:Promise.all(iterable) Tüm vaatlerin işlenmesi sırayla mı yoksa paralel mi? Veya daha spesifik olarak, zincirleme vaatler yürütmenin karşılığıdır.

p1.then(p2).then(p3).then(p4).then(p5)....

yoksa tüm algoritma diğer bazı tür p1, p2, p3, p4, p5, vb (paralel) aynı anda çağrılan ve sonuçlar tüm kararlılığı (veya bir ıskarta) en kısa sürede iade edilir?

S2: Eğer Promise.allparalel olarak çalışır, bir iterable sequencially çalıştırmak için uygun bir yol var?

Not : Q veya Bluebird kullanmak istemiyorum, ancak tüm yerel ES6 özellikleri.


Düğüm (V8) uygulaması veya spesifikasyon hakkında mı soruyorsunuz?
Amit

1
Promise.allOnları paralel olarak yürüttüğünden eminim .
royhowie

@Amit'i işaretledim node.jsve io.jskullandığım yer burası. Yani, evet, eğer V8 uygulaması olacak.
Yanick Rochon

9
Vaatler "icra edilemez". Görevlerine, yaratılırken başlarlar - yalnızca sonuçları temsil ederler - ve siz her şeyi onlara geçmeden önce paralel olarak yürütüyorsunuz Promise.all.
Bergi

Vaatler, yaratılış anında yapılır. (biraz kod çalıştırarak onaylanabilir). Gelen new Promise(a).then(b); c();bir birinci yürütülür, sonra C, sonra da b. Bu vaatleri yerine getiren Promise.all değil, sadece çözüldüklerinde işler.
Mateon1

Yanıtlar:


258

Is Promise.all(iterable)tüm sözleri yürütülmesi?

Hayır, vaatler "icra edilemez". Görevlerine, yaratılırken başlarlar - yalnızca sonuçları temsil ederler - ve siz her şeyi onlara geçmeden önce paralel olarak yürütüyorsunuz Promise.all.

Promise.allsadece birden fazla söz beklemektedir . Hangi sırayla çözdüklerini veya hesaplamaların paralel çalışıp çalışmadığını umursamıyor.

ardışık olarak tekrarlanabilir çalıştırmak için uygun bir yol var mı?

Eğer zaten vaat ettikleriniz varsa, fazla bir şey yapamazsınız Promise.all([p1, p2, p3, …])((bir dizi nosyonu yoktur). Ancak, eşzamanlı olmayan bir asenkron fonksiyonunuz varsa, bunları sırayla çalıştırabilirsiniz. Temelde almak gerekir

[fn1, fn2, fn3, …]

için

fn1().then(fn2).then(fn3).then(…)

ve bunu yapmak için çözüm Array::reduce:

iterable.reduce((p, fn) => p.then(fn), Promise.resolve())

1
Bu örnekte, çağrılabilir bir söz vermek istediğiniz işlevleri döndüren bir dizi işlev olabilir mi?
James Reategui

2
@SSHThis: Tam olarak thendizi gibidir - dönüş değeri son fnsonuç için vaattir ve buna diğer geri çağrıları zincirleyebilirsiniz.
Bergi

1
@wojjas Bu tam olarak eşdeğer fn1().then(p2).then(fn3).catch(…mi? İşlev ifadesi kullanmaya gerek yoktur.
Bergi

1
@wojjas Tabii ki retValFromF1içine aktarıldı p2, tam olarak p2bunu yapar. Elbette, daha fazlasını yapmak istiyorsanız (ek değişkenleri iletmek, birden fazla işlevi çağırmak vb.), Bir işlev ifadesi kullanmanız gerekir, ancak p2dizide değiştirmek daha kolay olacaktır
Bergi

1
@ robe007 Evet, demek iterableistediğim [fn1, fn2, fn3, …]dizi bu
Bergi

62

Paralel

await Promise.all(items.map(async item => { await fetchItem(item) }))

Avantajları: Daha hızlı. Biri başarısız olsa bile tüm yinelemeler yürütülür.

Sırayla

for (let i = 0; i < items.length; i++) {
    await fetchItem(items[i])
}

Avantajlar: Döngüdeki değişkenler her yineleme ile paylaşılabilir. Normal zorunlu eşzamanlı kod gibi davranır.


7
Veya:for (const item of items) await fetchItem(item);
Robert Penner

1
@david_adler Paralel örnek avantajlarda, tüm yinelemeler başarısız olsa bile yürütüleceğini söylediniz . Eğer yanılmıyorsam bu hala hızlı başarısız olur. Bu davranışı değiştirmek için aşağıdakine benzer bir şey yapabilirsiniz: await Promise.all(items.map(async item => { return await fetchItem(item).catch(e => e) }))
Taimoor

@Taimoor evet Promise.all'dan sonra "hızlı başarısız" ve kod yürütmeye devam ediyor, ancak tüm yinelemeler hala yürütülüyor codepen.io/mfbx9da4/pen/BbaaXr
david_adler

Bu yaklaşım, asyncişlev bir API çağrısı olduğunda ve sunucuyu DDOS yapmak istemediğinizde daha iyidir . Yürütmede atılan bireysel sonuçlar ve hatalar üzerinde daha iyi kontrole sahip olursunuz. Daha da iyisi, hangi hataların devam edeceğine ve neyin kırılacağına karar verebilirsiniz.
mandarin

JavaScript tek iş parçacıklı olduğundan, javascript aslında "paralel" iş parçacıkları kullanarak eşzamansız istekleri yürütmez unutmayın. developer.mozilla.org/tr-TR/docs/Web/JavaScript/EventLoop
david_adler

11

Bergis cevabı beni Array.reduce kullanarak doğru yola getirdi.

Ancak, aslında birbiri ardına yürütmek için sözlerimi dönen fonksiyonları almak için biraz daha yuvalama eklemek zorunda kaldı.

Benim gerçek kullanım durumum aşağı akış limitleri nedeniyle birbiri ardına aktarmak için gereken bir dosya dizisidir ...

İşte sonuçta.

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(() => {
            return transferFile(theFile); //function returns a promise
        });
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

Önceki yanıtların önerdiği gibi:

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(transferFile(theFile));
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

başka bir dosya başlatmadan önce aktarımın tamamlanmasını beklemedi ve ayrıca ilk dosya aktarımı başlamadan önce "Tüm dosyalar aktarıldı" metni geldi.

Neyi yanlış yaptığımdan emin değilim, ama benim için neyin işe yaradığını paylaşmak istedim.

Düzenleme: Bu yazıyı yazdığımdan beri neden ilk versiyonun işe yaramadığını anlıyorum. sonra () bir söz veren bir işlev bekler . Bu nedenle, işlev adını parantez olmadan geçmelisiniz! Şimdi, benim fonksiyonum bir argüman istiyor, bu yüzden anonim bir fonksiyonda hiç argüman almam gerekiyor!


4

sadece @ Bergi'nin cevabını detaylandırmak için (çok özlü, ama anlaşılması zor;)

Bu kod dizideki her öğeyi çalıştıracak ve bir sonraki 'sonra zinciri' sonuna ekleyecektir;

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());

umarım mantıklıdır.


3

Ayrıca yinelemeli bir işlev kullanarak bir eşzamansız işlevle tekrarlanabilir bir sırayla işleyebilirsiniz. Örneğin, aeşzamansız işlevle işlenecek bir dizi verildi someAsyncFunction():

var a = [1, 2, 3, 4, 5, 6]

function someAsyncFunction(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("someAsyncFunction: ", n)
      resolve(n)
    }, Math.random() * 1500)
  })
}

//You can run each array sequentially with: 

function sequential(arr, index = 0) {
  if (index >= arr.length) return Promise.resolve()
  return someAsyncFunction(arr[index])
    .then(r => {
      console.log("got value: ", r)
      return sequential(arr, index + 1)
    })
}

sequential(a).then(() => console.log("done"))


kullanmak array.prototype.reduce, performans açısından özyinelemeli bir işlevden çok daha iyidir
Mateusz Sowiński

@ MateuszSowiński, her çağrı arasında 1500 ms'lik bir zaman aşımı süresi var. Bunun sırayla zaman uyumsuz çağrılar yaptığını düşünürsek, çok hızlı bir zaman uyumsuz geri dönüş için bile bunun ne kadar alakalı olduğunu görmek zor.
Mark Meyer

Diyelim ki 40 hızlı asenkron işlevini birbiri ardına yürütmeniz gerekiyor - özyinelemeli işlevler kullanmak belleğinizi oldukça hızlı bir şekilde tıkayacaktır
Mateusz Sowiński

@ MateuszSowiński, yığının buraya sarılmadığını ... her görüşmeden sonra geri dönüyoruz. Bunu, tek bir adımda reducetüm then()zinciri inşa etmeniz gereken yerle karşılaştırın ve ardından çalıştırın.
Mark Meyer

Sıralı fonksiyonun 40. çağrısında, fonksiyonun ilk çağrısı hala ardışık fonksiyonlar zincirinin geri dönmesini bekliyor
Mateusz Sowiński

3

NodeJS vaatleri paralel olarak çalıştırmaz, tek bir iş parçacıklı olay döngü mimarisi olduğu için bunları eşzamanlı olarak çalıştırır. Çok çekirdekli CPU'dan yararlanmak için yeni bir alt süreç oluşturarak işleri paralel olarak çalıştırma imkanı vardır.

Paralel ve Uyumlu

Aslında, ne Promise.all vaat işlevini, onları aynı anda çalıştıran (olay döngüsü mimarisine bakın) uygun sırada sıraya koymak (P1, P2, ... çağırın) sonra her sonucu beklemek, sonra Promise.all'i tüm vaatlerle çözmek Sonuçlar. Promise.all, reddetmeyi kendiniz yönetmediğiniz sürece başarısız olan ilk sözde başarısız olacaktır.

Paralel ve eşzamanlı arasında büyük bir fark vardır, birincisi aynı işlemde ayrı bir işlemde farklı hesaplamalar yürütecek ve orada ritm ilerleyecekken, diğeri bir öncekini beklemeden farklı hesaplamayı birbiri ardına gerçekleştirecektir. aynı anda bitirmek ve ilerlemek için hesaplama birbirlerine bağlı olmadan.

Son olarak, sorunuzu cevaplamak için Promise.allne paralel ne de sırayla değil, eşzamanlı olarak yürütülmez.


Bu doğru değil. NodeJS işleri paralel olarak yürütebilir. NodeJS'nin bir iş parçacığı kavramı vardır. Varsayılan olarak, çalışan iş parçacığı sayısı 4'tür. Örneğin, iki değeri karmak için kripto kitaplığı kullanırsanız, bunları paralel olarak yürütebilirsiniz. İki işçi iş parçacığı görevi üstlenir. Tabii ki, CPU'nuz paralelliğin desteklenmesi için çok çekirdekli olmalıdır.
Şihab

Evet haklısın, ilk paragrafın sonunda söylediklerim, ama çocuk sürecinden bahsettim, elbette işçi çalıştırabilirler.
Adrien De Peretti

2

Kullanma uyumsuz beklemektedir vaat bir dizi kolayca sırayla yürütülebilir:

let a = [promise1, promise2, promise3];

async function func() {
  for(let i=0; i<a.length; i++){
    await a[i]();
  }  
}

func();

Not: Bir söz reddedilmesi halinde yukarıdaki uygulamada, gerisi tüm sözler, sonra sarmak yürütülecek istiyorum executed.If olmaz await a[i]();içinitry catch


2

paralel

bu örneğe bak

const resolveAfterTimeout = async i => {
  return new Promise(resolve => {
    console.log("CALLED");
    setTimeout(() => {
      resolve("RESOLVED", i);
    }, 5000);
  });
};

const call = async () => {
  const res = await Promise.all([
    resolveAfterTimeout(1),
    resolveAfterTimeout(2),
    resolveAfterTimeout(3),
    resolveAfterTimeout(4),
    resolveAfterTimeout(5),
    resolveAfterTimeout(6)
  ]);
  console.log({ res });
};

call();

kodu çalıştırarak altı vaat için de "CALLED" yazacak ve çözüldüklerinde, zaman aşımından sonra her 6 yanıtta bir araya gelecektir.


1

Bergi'nin cevabı çağrıyı senkronize etmeme yardımcı oldu.Önceki fonksiyon çağrıldıktan sonra her fonksiyonu çağırdığımız yere bir örnek ekledim.

function func1 (param1) {
    console.log("function1 : " + param1);
}
function func2 () {
    console.log("function2");
}
function func3 (param2, param3) {
    console.log("function3 : " + param2 + ", " + param3);
}

function func4 (param4) {
    console.log("function4 : " + param4);
}
param4 = "Kate";

//adding 3 functions to array

a=[
    ()=>func1("Hi"),
    ()=>func2(),
    ()=>func3("Lindsay",param4)
  ];

//adding 4th function

a.push(()=>func4("dad"));

//below does func1().then(func2).then(func3).then(func4)

a.reduce((p, fn) => p.then(fn), Promise.resolve());

Bu orijinal sorunun cevabı mı?
Giulio Caccin

0

Bunu döngü için yapabilirsiniz.

zaman uyumsuz işlev dönüş sözü

async function createClient(client) {
    return await Client.create(client);
}

let clients = [client1, client2, client3];

Aşağıdaki kodu yazarsanız, istemci paralel olarak oluşturulur

const createdClientsArray = yield Promise.all(clients.map((client) =>
    createClient(client);
));

sonra tüm müşteriler paralel olarak oluşturulur. ancak istemciyi sırayla oluşturmak istiyorsanız döngü için kullanmalısınız

const createdClientsArray = [];
for(let i = 0; i < clients.length; i++) {
    const createdClient = yield createClient(clients[i]);
    createdClientsArray.push(createdClient);
}

sonra tüm istemciler sırayla oluşturulur.

mutlu kodlama :)


8
Şu anda, async/ awaityalnızca bir transpiler ile veya Düğüm dışında başka motorlar kullanılarak kullanılabilir . Ayrıca, gerçekten karıştırmak olmamalıdır asyncile yield. Bir transpiler ile aynı şekilde davranırlarsa ve cogerçekten oldukça farklıdırlar ve normalde birbirlerinin yerine geçmemelidirler. Ayrıca, cevabınız acemi programcılar için kafa karıştırıcı olduğu için bu kısıtlamalardan bahsetmelisiniz.
Yanick Rochon

0

Sıralı vaatleri çözmek için kullanıyorum. Burada yardımcı olup olmadığından emin değilim ama bunu yapıyorum.

async function run() {
    for (let val of arr) {
        const res = await someQuery(val)
        console.log(val)
    }
}

run().then().catch()

0

bu sorunuzun bir kısmına cevap verebilir.

evet, bir dizi geri dönen fonksiyon zincirini aşağıdaki gibi zincirleyebilirsiniz ... (bu her fonksiyonun sonucunu bir sonrakine iletir). her işleve aynı argümanı iletmek (veya argüman olmamak) için elbette düzenleyebilirsiniz.

function tester1(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a + 1);
    }, 1000);
  })
}

function tester2(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a * 5);
    }, 1000);
  })
}

function promise_chain(args, list, results) {

  return new Promise(function(done, errs) {
    var fn = list.shift();
    if (results === undefined) results = [];
    if (typeof fn === 'function') {
      fn(args).then(function(result) {
        results.push(result);
        console.log(result);
        promise_chain(result, list, results).then(done);
      }, errs);
    } else {
      done(results);
    }

  });

}

promise_chain(0, [tester1, tester2, tester1, tester2, tester2]).then(console.log.bind(console), console.error.bind(console));


0

NodeJS'de bir sorunu çözmeye çalışırken bu sayfada karşılaştım: dosya parçalarının yeniden birleştirilmesi. Temel olarak: Bir dizi dosya ismim var. Tek bir büyük dosya oluşturmak için tüm bu dosyaları doğru sırada eklemem gerekiyor. Bunu eşzamansız olarak yapmalıyım.

Düğüm 'fs' modülü appendFileSync sağlar, ancak bu işlem sırasında sunucuyu engellemek istemiyordu. Ben fs.promises modülünü kullanmak ve bunları birbirine zincirlemek için bir yol bulmak istedim. Aslında iki işleme ihtiyacım olduğu için bu sayfadaki örnekler benim için pek işe yaramadı: chs dosyasında okumak için fsPromises.read () ve hedef dosyaya uyum sağlamak için fsPromises.appendFile (). Belki javascript ile daha iyi olsaydı önceki cevapları benim için işe yarayabilir. ;-)

Ben bununla karşılaştım ... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ... ve çalışan bir çözümü hackleyebildim.

TLDR:

/**
 * sequentially append a list of files into a specified destination file
 */
exports.append_files = function (destinationFile, arrayOfFilenames) {
    return arrayOfFilenames.reduce((previousPromise, currentFile) => {
        return previousPromise.then(() => {
            return fsPromises.readFile(currentFile).then(fileContents => {
                return fsPromises.appendFile(destinationFile, fileContents);
            });
        });
    }, Promise.resolve());
};

Ve işte bir yasemin birimi testi:

const fsPromises = require('fs').promises;
const fsUtils = require( ... );
const TEMPDIR = 'temp';

describe("test append_files", function() {
    it('append_files should work', async function(done) {
        try {
            // setup: create some files
            await fsPromises.mkdir(TEMPDIR);
            await fsPromises.writeFile(path.join(TEMPDIR, '1'), 'one');
            await fsPromises.writeFile(path.join(TEMPDIR, '2'), 'two');
            await fsPromises.writeFile(path.join(TEMPDIR, '3'), 'three');
            await fsPromises.writeFile(path.join(TEMPDIR, '4'), 'four');
            await fsPromises.writeFile(path.join(TEMPDIR, '5'), 'five');

            const filenameArray = [];
            for (var i=1; i < 6; i++) {
                filenameArray.push(path.join(TEMPDIR, i.toString()));
            }

            const DESTFILE = path.join(TEMPDIR, 'final');
            await fsUtils.append_files(DESTFILE, filenameArray);

            // confirm "final" file exists    
            const fsStat = await fsPromises.stat(DESTFILE);
            expect(fsStat.isFile()).toBeTruthy();

            // confirm content of the "final" file
            const expectedContent = new Buffer('onetwothreefourfive', 'utf8');
            var fileContents = await fsPromises.readFile(DESTFILE);
            expect(fileContents).toEqual(expectedContent);

            done();
        }
        catch (err) {
            fail(err);
        }
        finally {
        }
    });
});

Umarım birine yardımcı olur.

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.