Promise.all: Urutan nilai yang diselesaikan

189

Melihat MDN sepertinya valuesditeruskan ke then()callback of Promise. Semua berisi nilai-nilai dalam urutan janji. Sebagai contoh:

var somePromises = [1, 2, 3, 4, 5].map(Promise.resolve);
return Promise.all(somePromises).then(function(results) {
  console.log(results) //  is [1, 2, 3, 4, 5] the guaranteed result?
});

Adakah yang bisa mengutip spec yang menyatakan urutannya values?

PS: Menjalankan kode seperti itu menunjukkan bahwa ini kelihatannya benar meskipun tentu saja tidak ada bukti - itu bisa saja kebetulan.

Thorben Croisé
sumber

Jawaban:

274

Singkatnya, pesanan dipertahankan .

Mengikuti spec yang Anda tautkan, Promise.all(iterable)mengambil iterable(yaitu, objek yang mendukung Iteratorantarmuka) sebagai parameter dan kemudian pada panggilan PerformPromiseAll( iterator, constructor, resultCapability)dengan itu, di mana yang terakhir loop iterablemenggunakan IteratorStep(iterator).
Ini berarti bahwa jika iterable yang Anda lewati Promise.all()diperintahkan secara ketat, mereka akan tetap diperintahkan begitu dilewati.

Penyelesaian diimplementasikan melalui Promise.all() Resolvetempat setiap janji yang diselesaikan memiliki [[Index]]slot internal , yang menandai indeks janji di input asli.


Semua ini berarti bahwa output secara ketat dipesan sebagai input selama input benar-benar dipesan (misalnya, sebuah array).

Anda dapat melihat ini beraksi di biola di bawah ini (ES6):

// Used to display results
const write = msg => {
  document.body.appendChild(document.createElement('div')).innerHTML = msg;
};

// Different speed async operations
const slow = new Promise(resolve => {
  setTimeout(resolve, 200, 'slow');
});
const instant = 'instant';
const quick = new Promise(resolve => {
  setTimeout(resolve, 50, 'quick');
});

// The order is preserved regardless of what resolved first
Promise.all([slow, instant, quick]).then(responses => {
  responses.map(response => write(response));
});

Nit
sumber
1
Bagaimana iterable tidak dapat dipesan secara ketat? Setiap iterable adalah "benar-benar dipesan" oleh perintah yang menghasilkan nilai-nilainya.
Benjamin Gruenbaum
Catatan - Firefox adalah satu-satunya browser yang mengimplementasikan iterables dalam janji dengan benar. Chrome saat ini akan throwmenjadi pengecualian jika Anda meneruskannya ke Promise.all. Selain itu, saya tidak mengetahui adanya implementasi janji pengguna lahan yang saat ini mendukung kelulusan iterables meskipun banyak yang memperdebatkannya dan memutuskan untuk tidak melakukannya pada saat itu.
Benjamin Gruenbaum
3
@BenjaminGruenbaum. Mungkinkah memiliki iterable yang menghasilkan dua pesanan berbeda setelah di-iterasi dua kali? Sebagai contoh, setumpuk kartu yang menghasilkan kartu dalam urutan acak saat iterasi? Saya tidak tahu apakah "benar-benar dipesan" adalah terminologi yang tepat di sini, tetapi tidak semua iterables memiliki pesanan tetap. Jadi saya pikir masuk akal untuk mengatakan bahwa iterator "benar-benar dipesan" (dengan asumsi itu istilah yang tepat), tetapi iterables tidak.
JLRishe
3
@ JLRishe Saya kira Anda benar, memang iterator yang dipesan - iterables tidak.
Benjamin Gruenbaum
8
Patut dicatat bahwa janji-janji itu tidak berantai. Meskipun Anda akan mendapatkan resolusi dalam urutan yang sama, tidak ada jaminan kapan janji itu ditindaklanjuti. Dengan kata lain, Promise.alltidak bisa digunakan untuk menjalankan berbagai janji secara berurutan, satu demi satu. Janji-janji yang dimuat ke dalam iterator harus independen satu sama lain agar ini dapat diprediksi.
Andrew Eddie
49

Seperti jawaban sebelumnya telah menyatakan, Promise.allmenggabungkan semua nilai yang diselesaikan dengan array yang sesuai dengan urutan input dari Janji asli (lihat Janji Agregat ).

Namun, saya ingin menunjukkan, bahwa pesanan hanya dipertahankan di sisi klien!

Bagi pengembang sepertinya Janji itu dipenuhi secara berurutan, tetapi dalam kenyataannya, Janji tersebut diproses dengan kecepatan yang berbeda. Ini penting untuk diketahui ketika Anda bekerja dengan backend jarak jauh karena backend mungkin menerima Janji Anda dalam urutan yang berbeda.

Berikut adalah contoh yang menunjukkan masalah dengan menggunakan batas waktu:

Janji

const myPromises = [
  new Promise((resolve) => setTimeout(() => {resolve('A (slow)'); console.log('A (slow)')}, 1000)),
  new Promise((resolve) => setTimeout(() => {resolve('B (slower)'); console.log('B (slower)')}, 2000)),
  new Promise((resolve) => setTimeout(() => {resolve('C (fast)'); console.log('C (fast)')}, 10))
];

Promise.all(myPromises).then(console.log)

Dalam kode yang ditunjukkan di atas, tiga Janji (A, B, C) diberikan kepada Promise.all. Tiga Janji dijalankan dengan kecepatan yang berbeda (C menjadi yang tercepat dan B menjadi yang paling lambat). Itu sebabnya console.logpernyataan Janji muncul dalam urutan ini:

C (fast) 
A (slow)
B (slower)

Jika Janji adalah panggilan AJAX, maka backend jarak jauh akan menerima nilai-nilai ini dalam urutan ini. Tetapi di sisi klien Promise.allmemastikan bahwa hasilnya dipesan sesuai dengan posisi asli myPromisesarray. Itu sebabnya hasil akhirnya adalah:

['A (slow)', 'B (slower)', 'C (fast)']

Jika Anda ingin menjamin juga pelaksanaan aktual Janji Anda, maka Anda akan memerlukan konsep seperti antrian Janji. Berikut ini adalah contoh menggunakan p-antrian (hati-hati, Anda harus membungkus semua Janji dalam fungsi):

Antrian Janji Berurutan

const PQueue = require('p-queue');
const queue = new PQueue({concurrency: 1});

// Thunked Promises:
const myPromises = [
  () => new Promise((resolve) => setTimeout(() => {
    resolve('A (slow)');
    console.log('A (slow)');
  }, 1000)),
  () => new Promise((resolve) => setTimeout(() => {
    resolve('B (slower)');
    console.log('B (slower)');
  }, 2000)),
  () => new Promise((resolve) => setTimeout(() => {
    resolve('C (fast)');
    console.log('C (fast)');
  }, 10))
];

queue.addAll(myPromises).then(console.log);

Hasil

A (slow)
B (slower)
C (fast)

['A (slow)', 'B (slower)', 'C (fast)']
Benny Neugebauer
sumber
2
jawaban yang bagus, terutama menggunakan PQueue
ironstein
Saya memerlukan Antrian Janji Berurutan, tetapi bagaimana melakukannya jika saya harus melakukannya dari catatan hasil sql? dalam untuk? sementara?, tidak ada alternatif di ES2017 ES2018 kami?
stackdave
PQueue membantu saya! Terima kasih! :)
podeig
28

Ya, nilai dalam resultsberada dalam urutan yang sama dengan promises.

Orang mungkin mengutip spec ES6 padaPromise.all , meskipun itu agak berbelit-belit karena api iterator yang digunakan dan konstruktor janji generik. Namun, Anda akan melihat bahwa setiap callback resolver memiliki [[index]]atribut yang dibuat dalam iterasi janji-array dan digunakan untuk mengatur nilai pada array hasil.

Bergi
sumber
Aneh, saya melihat video youtube hari ini yang mengatakan bahwa urutan output ditentukan oleh yang pertama diselesaikan, kemudian kedua, lalu ..... Saya kira video OP salah?
Royi Namir
1
@RoyiNamir: Rupanya dia.
Bergi
@ Ozil Wat? Urutan resolusi kronologis sama sekali tidak masalah ketika semua janji dipenuhi. Urutan nilai dalam array hasil sama dengan di array input janji. Jika tidak, Anda harus beralih ke implementasi janji yang tepat.
Bergi