Paralel olarak asenkron / bekleme işlevlerini çağırma


431

Anladığım kadarıyla, ES7 / ES2016'da çoklu kodlar koymak , vaatlerle awaitzincirlemeye benzer şekilde çalışacak .then(), yani parallerl yerine birbiri ardına çalışacaklar. Örneğin, şu kodumuz var:

await someCall();
await anotherCall();

anotherCall()Sadece someCall()tamamlandığında çağrılacak doğru mu anladım ? Onları paralel olarak çağırmanın en zarif yolu nedir?

Düğümde kullanmak istiyorum, bu yüzden belki async kütüphanesi ile bir çözüm var mı?

DÜZENLEME: Bu soruda sağlanan çözümden memnun değilim: Asenkron jeneratörlerde vaatlerin paralel olarak beklemesinden dolayı yavaşlama , çünkü jeneratör kullanıyor ve daha genel bir kullanım durumu soruyorum.


1
@adeneo Bu yanlıştır, Javascript asla kendi bağlamında paralel olarak çalışmaz.
Blindman67

5
@ Blindman67 - en azından OP'nin iki şekilde eşzamanlı olmayan işlemlerin eşzamanlı çalıştığı anlamına gelir, ancak bu durumda yazmam gereken şey, seri olarak çalışmalarıydı , ilk awaitolarak ilk işlevin tamamlanmasını beklerdi tamamen ikinciyi çalıştırmadan önce.
adeneo

3
Blindman67 @ - bu tek dişli, ama bu sınırlama zaman uyumsuz yöntemlerine için geçerli değildir, onlar olabilir onlar, yani neyi "Parallell" tarafından OP araçlarının bittiğinde yanıtı aynı anda çalışabilir ve dönüş.
adeneo

7
Blindman67 - Bence OP soruyor oldukça açıktır, async / await desen kullanarak fonksiyonları async olsa bile seri olarak çalışmasını sağlayacaktır, bu yüzden ilk olarak ikinci denir önce tamamen bitirir. OP olduğunu parallell'de her iki işlevin nasıl çağrılacağını sormak ve açıkça eşzamansız olmaları nedeniyle, amaç onları eşzamanlı olarak çalıştırmaktır, örneğin paralel olarak, örneğin iki ajax isteğini aynı anda yapmak, çoğu JavaScript'te olduğu gibi, çoğu async yöntemi gibi bir sorun değildir , belirttiğiniz gibi, yerel kodu çalıştırır ve daha fazla iş parçacığı kullanır.
adeneo

3
@Bergi bu bağlantılı sorunun bir kopyası değil - bu özellikle async / await sözdizimi ve yerli Promises ile ilgilidir. Bağlantılı soru jeneratörler ve verim ile mavi kuş kütüphanesi ile ilgilidir. Kavramsal olarak belki de benzer, ama uygulamada değil.
Iest

Yanıtlar:


700

Bekleyebilirsiniz Promise.all():

await Promise.all([someCall(), anotherCall()]);

Sonuçları saklamak için:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Bunun Promise.allhızlı bir şekilde başarısız olduğunu unutmayın; bu , kendisine verilen sözlerden biri reddedildiği anda, her şeyin reddedildiği anlamına gelir.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
  .then(console.log).catch(console.log) // 'sad'

Bunun yerine, tüm vaatlerin yerine getirilmesini veya reddedilmesini beklemek istiyorsanız, kullanabilirsiniz Promise.allSettled. Internet Explorer'ın bu yöntemi yerel olarak desteklemediğini unutmayın.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.allSettled([happy('happy', 100), sad('sad', 50)])
  .then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]


78
Temizleyin ama Promise.all'ın hızlı başarısız davranışının farkında olun. İşlevlerin herhangi bir hata atarsa, Promise.all reddedecektir
NoNameProvided

11
Sen güzel ile zaman uyumsuz / bekliyoruz, bkz kısmi sonuçlara işleyebilir stackoverflow.com/a/42158854/2019689
NoNameProvided

131
Profesyonel ipucu: Promise.all () öğesinden rasgele sayıda sonuç başlatmak için dizi yıkımını kullanın, örneğin:[result1, result2] = Promise.all([async1(), async2()]);
jonny

10
@jonny Bu konu hızlı başarısız oluyor mu? Ayrıca, birinin hala ihtiyacı var mı = await Promise.all?
theUtherSide

5
@theUtherSide Kesinlikle haklısın - Beklemeyi dahil etmeyi ihmal ettim.
jonny

114

TL; DR

Promise.allParalel işlev çağrıları için kullanın , hata oluştuğunda yanıt davranışları doğru değil.


İlk olarak, tüm eşzamansız çağrıları bir kerede yürütün ve tüm Promisenesneleri alın. İkincisi, nesneler awaitüzerinde kullanın Promise. Bu şekilde, Promisediğer asenkron çağrıları çözümlemek için ilk beklerken hala devam ediyor. Genel olarak, yalnızca en yavaş eşzamansız çağrı kadar bekleyeceksiniz. Örneğin:

// Begin first call and store promise without waiting
const someResult = someCall();

// Begin second call and store promise without waiting
const anotherResult = anotherCall();

// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];

// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise

JSbin örneği: http://jsbin.com/xerifanima/edit?js,console

Uyarı:await İlk awaitçağrı , tüm eşzamansız çağrılardan sonra gerçekleştiği sürece , çağrıların aynı hatta veya farklı hatlarda olması önemli değildir . JohnnyHK'nın yorumuna bakın.


Güncelleme: Bu yanıt göre hata işleme farklı bir zamanlaması vardır @ Bergi cevabı , bu mu DEĞİL tüm sözler yürütülür sonra hata oluşur olarak hatayı dışarı atmak ama. Sonucu @ jonny'nin ipucuyla karşılaştırırım: [result1, result2] = Promise.all([async1(), async2()])Aşağıdaki kod snippet'ini kontrol et

const correctAsync500ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, 'correct500msResult');
  });
};

const correctAsync100ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, 'correct100msResult');
  });
};

const rejectAsync100ms = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'reject100msError');
  });
};

const asyncInArray = async (fun1, fun2) => {
  const label = 'test async functions in array';
  try {
    console.time(label);
    const p1 = fun1();
    const p2 = fun2();
    const result = [await p1, await p2];
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

const asyncInPromiseAll = async (fun1, fun2) => {
  const label = 'test async functions with Promise.all';
  try {
    console.time(label);
    let [value1, value2] = await Promise.all([fun1(), fun2()]);
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

(async () => {
  console.group('async functions without error');
  console.log('async functions without error: start')
  await asyncInArray(correctAsync500ms, correctAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
  console.groupEnd();

  console.group('async functions with error');
  console.log('async functions with error: start')
  await asyncInArray(correctAsync500ms, rejectAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
  console.groupEnd();
})();


11
Promise.all daha bana daha hoş seçenek gibi bu görünüyor - ve hatta yapabilirsiniz atama kurucuların ile [someResult, anotherResult] = [await someResult, await anotherResult]değiştirmek eğer constiçin let.
jawj

28
Ama bu hala awaitifadeleri seri olarak yürütüyor , değil mi? Yani, yürütme ilk çözülene kadar duraklar await, sonra ikinciye geçer. Promise.allparalel olarak yürütür.
Andru

8
Teşekkürler @Haven. Bu kabul edilen cevap olmalı.
Stefan D

87
Bu cevap yanıltıcıdır, çünkü her iki beklemenin aynı çizgide yapılması gerçeği önemsizdir. Önemli olan, iki eşzamansız çağrının ikisinden de önce yapılmasıdır.
JohnnyHK

15
@ Bu çözüm ile aynı değildir Promise.all. Her istek bir şebeke çağrısı ise, await someResultbaşlamadan önce çözülmesi gerekir await anotherResult. Tersine, Promise.alliki awaitçağrıda biri çözülmeden önce başlatılabilir.
Ben Winding

89

Güncelleme:

Orijinal cevap, vaat reddini doğru bir şekilde ele almayı zorlaştırır (ve bazı durumlarda imkansızdır). Doğru çözüm kullanmaktır Promise.all:

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Orijinal cevap:

İkisini de beklemeden önce her iki işlevi de aradığınızdan emin olun:

// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;

1
@JeffFischer Umarım açıklığa kavuşturacak yorumlar ekledim.
Jonathan Potter

9
Bu kesinlikle en saf cevap gibi hissediyorum
Gershom

1
Bu cevap Haven'dan çok daha açık. İşlev çağrılarının söz verilen nesneleri döndüreceği ve awaitbunları gerçek değerlere çözeceği açıktır .
user1032613

3
Bu, lanetli bir bakışta işe yarıyor gibi görünüyor, ancak ele alınmayan reddetmelerle ilgili korkunç sorunları var . Bunu kullanmayın!
Bergi

1
@Bergi Haklısın, işaret ettiğin için teşekkürler! Cevabı daha iyi bir çözümle güncelledim.
Jonathan Potter

24

Promise.all () olmadan paralel olarak yapmanın başka bir yolu vardır:

İlk olarak, sayıları yazdırmak için 2 fonksiyonumuz var:

function printNumber1() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number1 is done");
      resolve(10);
      },1000);
   });
}

function printNumber2() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number2 is done");
      resolve(20);
      },500);
   });
}

Bu sıralı:

async function oneByOne() {
   const number1 = await printNumber1();
   const number2 = await printNumber2();
} 
//Output: Number1 is done, Number2 is done

Bu paraleldir:

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done

10

Bu , başarısız-hızlı davranışı olmayan ancak buna benzer Promise.allSettled () ile gerçekleştirilebilir Promise.all().

async function failure() {
    throw "Failure!";
}

async function success() {
    return "Success!";
}

const [failureResult, successResult] = await Promise.allSettled([failure(), success()]);

console.log(failureResult); // {status: "rejected", reason: "Failure!"}
console.log(successResult); // {status: "fulfilled", value: "Success!"}

Not : Bu, sınırlı tarayıcı desteğine sahip yeni bir özelliktir, bu nedenle bu işlev için bir çoklu dolgu eklemenizi şiddetle tavsiye ederim.



1
    // A generic test function that can be configured 
    // with an arbitrary delay and to either resolve or reject
    const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
        console.log(`Done ${ delay }`);
        resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
    }, delay));

    // Our async handler function
    const handler = async () => {
        // Promise 1 runs first, but resolves last
        const p1 = test(10000, true);
        // Promise 2 run second, and also resolves
        const p2 = test(5000, true);
        // Promise 3 runs last, but completes first (with a rejection) 
        // Note the catch to trap the error immediately
        const p3 = test(1000, false).catch(e => console.log(e));
        // Await all in parallel
        const r = await Promise.all([p1, p2, p3]);
        // Display the results
        console.log(r);
    };

    // Run the handler
    handler();
    /*
    Done 1000
    Reject 1000
    Done 5000
    Done 10000
    */

P1, p2 ve p3 ayarları kesinlikle paralel olarak çalıştırılmasa da, herhangi bir yürütme yapmazlar ve bağlamsal hataları bir yakalama ile yakalayabilirsiniz.


2
Stack Overflow'a hoş geldiniz. Kodunuz sorunun cevabını verebilirken, lütfen etrafına bağlam ekleyin, böylece diğerleri ne yaptığını ve neden orada olduğunu bilmelidir.
Theo

1

Benim durumumda, paralel olarak yürütmek istediğim birkaç görevim var, ancak bu görevlerin sonucuyla farklı bir şey yapmam gerekiyor.

function wait(ms, data) {
    console.log('Starting task:', data, ms);
    return new Promise(resolve => setTimeout(resolve, ms, data));
}

var tasks = [
    async () => {
        var result = await wait(1000, 'moose');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(500, 'taco');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(5000, 'burp');
        // do something with result
        console.log(result);
    }
]

await Promise.all(tasks.map(p => p()));
console.log('done');

Ve çıktı:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done

cool for dynamic creation (kaynak dizisi)
Michal Miky Jankovský

1

await Promise.all ([someCall (), anotherCall ()]); daha önce de belirtildiği gibi bir iplik çiti olarak hareket edecektir (CUDA olarak paralel kodda çok yaygındır), bu nedenle içindeki tüm vaatlerin birbirini engellemeden çalışmasına izin verecektir, ancak TÜM çözülene kadar yürütmenin devam etmesini önleyecektir.

paylaşmaya değer başka bir yaklaşım, görev doğrudan API çağrısı, G / Ç işlemleri olarak sınırlı kaynakların kullanımı ile doğrudan bağlantılıysa, genellikle istenen eşzamanlılık miktarını kolayca kontrol etmenizi sağlayacak Node.js zaman uyumsuzluğudur. vb.

// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
  console.log('Hello ' + task.name);
  callback();
}, 2);

// assign a callback
q.drain = function() {
  console.log('All items have been processed');
};

// add some items to the queue
q.push({name: 'foo'}, function(err) {
  console.log('Finished processing foo');
});

q.push({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

// add some items to the queue (batch-wise)
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
  console.log('Finished processing item');
});

// add some items to the front of the queue
q.unshift({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

Orta makale autor'a verilen krediler ( daha fazla bilgi edinin )


-5

Ben oylarım:

await Promise.all([someCall(), anotherCall()]);

İşlevleri aradığınız andan haberdar olun, bu beklenmeyen sonuçlara neden olabilir:

// Supposing anotherCall() will trigger a request to create a new User

if (callFirst) {
  await someCall();
} else {
  await Promise.all([someCall(), anotherCall()]); // --> create new User here
}

Ancak aşağıdakiler her zaman yeni Kullanıcı oluşturma isteğini tetikler

// Supposing anotherCall() will trigger a request to create a new User

const someResult = someCall();
const anotherResult = anotherCall(); // ->> This always creates new User

if (callFirst) {
  await someCall();
} else {
  const finalResult = [await someResult, await anotherResult]
}

Fonksiyonu koşul testinin dışında / öncesinde bildirdiğiniz ve onları çağırdığınız için. Bunları elseblok halinde sarmayı deneyin .
Haven

@Haven: Eğer anları ayırmak ne demek diyoruz vs fonksiyonları bekliyor örneğin, beklenmedik sonuçlara yol açabilir: zaman uyumsuz HTTP isteği.
Hoang Le Anh Tu

-6

Ben bir yardımcı işlevi waitAll oluşturmak, belki daha tatlı yapabilirsiniz. Sadece çalışır nodejs , şimdilik değil tarayıcı krom.

    //const parallel = async (...items) => {
    const waitAll = async (...items) => {
        //this function does start execution the functions
        //the execution has been started before running this code here
        //instead it collects of the result of execution of the functions

        const temp = [];
        for (const item of items) {
            //this is not
            //temp.push(await item())
            //it does wait for the result in series (not in parallel), but
            //it doesn't affect the parallel execution of those functions
            //because they haven started earlier
            temp.push(await item);
        }
        return temp;
    };

    //the async functions are executed in parallel before passed
    //in the waitAll function

    //const finalResult = await waitAll(someResult(), anotherResult());
    //const finalResult = await parallel(someResult(), anotherResult());
    //or
    const [result1, result2] = await waitAll(someResult(), anotherResult());
    //const [result1, result2] = await parallel(someResult(), anotherResult());

3
Hayır, paralellik burada gerçekleşmiyor. forDöngü sırayla her sözünü bekliyor ve diziye sonucunu ekler.
Szczepan Hołyszewski

Bunun insanlar için işe yaramadığını anlıyorum. Bu yüzden node.js ve tarayıcıda test ettim. Test node.js (v10, v11), firefox'ta geçirilir, tarayıcı kromunda çalışmaz. Test örneği gist.github.com/fredyang/ea736a7b8293edf7a1a25c39c7d2fbbf
Fred Yang

2
Buna inanmayı reddediyorum. Standartta, bir for döngüsünün farklı yinelemelerinin otomatik olarak paralelleştirilebileceğini söyleyen hiçbir şey yoktur; javascript bu şekilde çalışmaz. Döngü kodunun yazılma şekli şu anlama gelir : "bir öğe bekliyor (ifadeyi bekliyor), SONRA sonuca geçici olarak it, sonra bir sonraki öğeyi al (for döngüsünün bir sonraki yinelemesi). Her öğe için" bekliyor "tamamen Testler paralelleştirme olduğunu gösteriyorsa, bunun nedeni transpilerin standart olmayan bir şey yapması veya düz bir arabası olması olmalıdır.
Szczepan Hołyszewski

@ SzczepanHołyszewski Test davasını çalıştırmadan disbieving güveniniz bana bazı yeniden adlandırma ve ekstra yorum yapmak için ilham veriyor. Tüm kodlar eski ES6 kodudur, aktarım gerekmez.
Fred Yang

Bunun neden bu kadar ağır indirildiğinden emin değilim. Temelde @ user2883596'nın verdiği yanıtla aynı.
Jonathan Sudiaman
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.