Gunakan async, tunggu dengan Array.map

170

Diberikan kode berikut:

var arr = [1,2,3,4,5];

var results: number[] = await arr.map(async (item): Promise<number> => {
        await callAsynchronousOperation(item);
        return item + 1;
    });

yang menghasilkan kesalahan berikut:

TS2322: Ketik 'Janji <nomor> []' tidak dapat ditentukan untuk mengetik 'angka []'. Ketik 'Janji <nomor> tidak dapat ditentukan untuk mengetik' angka '.

Bagaimana saya bisa memperbaikinya? Bagaimana saya bisa membuat async awaitdan Array.mapbekerja bersama?

Alon
sumber
6
Mengapa Anda mencoba membuat operasi sinkron menjadi operasi async? arr.map()sinkron dan tidak mengembalikan janji.
jfriend00
2
Anda tidak dapat mengirim operasi asinkron ke fungsi, seperti map, yang mengharapkan operasi sinkron, dan berharap itu berfungsi.
Monyet
1
@ jfriend00 Saya punya banyak pernyataan menunggu di fungsi batin. Ini sebenarnya adalah fungsi yang panjang dan saya hanya menyederhanakannya agar dapat dibaca. Saya telah menambahkan sekarang panggilan tunggu untuk membuatnya lebih jelas mengapa harus async.
Alon
Anda harus menunggu sesuatu yang mengembalikan janji, bukan sesuatu yang mengembalikan array.
jfriend00
2
Satu hal yang berguna untuk disadari adalah bahwa setiap kali Anda menandai suatu fungsi sebagai async, Anda membuat fungsi itu mengembalikan janji. Jadi tentu saja, peta async mengembalikan sejumlah janji :)
Anthony Manning-Franklin

Jawaban:

380

Masalahnya di sini adalah bahwa Anda mencoba awaitberbagai janji daripada janji. Ini tidak melakukan apa yang Anda harapkan.

Ketika objek yang diteruskan awaitbukan Janji, awaitcukup kembalikan nilai apa adanya segera daripada mencoba menyelesaikannya. Jadi sejak Anda melewati awaitarray (objek Promise) di sini alih-alih Janji, nilai yang dikembalikan dengan menunggu hanyalah array itu, yang bertipe Promise<number>[].

Apa yang perlu Anda lakukan di sini adalah memanggil Promise.allarray yang dikembalikan oleh mapuntuk mengubahnya menjadi Janji tunggal sebelum awaiting itu.

Menurut dokumen MDN untukPromise.all :

The Promise.all(iterable)method mengembalikan janji yang resolve ketika semua janji-janji dalam argumen iterable telah diselesaikan, atau menolak dengan alasan janji pertama berlalu itu menolak.

Jadi dalam kasus Anda:

var arr = [1, 2, 3, 4, 5];

var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
}));

Ini akan menyelesaikan kesalahan spesifik yang Anda temui di sini.

Ajedi32
sumber
1
Apa yang dimaksud dengan :titik dua?
Daniel berkata Reinstate Monica
12
@DanielPendergast Ini untuk anotasi jenis dalam TypeScript.
Ajedi32
Apa perbedaan antara memanggil callAsynchronousOperation(item);dengan dan tanpa awaitdi dalam fungsi peta async?
nerdizzle
@nerdizzle Kedengarannya seperti kandidat yang baik untuk pertanyaan lain. Pada dasarnya, dengan awaitfungsi akan menunggu operasi asinkron untuk menyelesaikan (atau gagal) sebelum melanjutkan, jika tidak maka akan segera dilanjutkan tanpa menunggu.
Ajedi32
@ Ajedi32 thx atas tanggapannya. Tetapi tanpa menunggu di peta async tidak mungkin lagi untuk menunggu hasil dari fungsi?
nerdizzle
16

Ada solusi lain untuk itu jika Anda tidak menggunakan Janji asli tetapi Bluebird.

Anda juga dapat mencoba menggunakan Promise.map () , mencampur array.map dan Promise.all

Jika Anda:

  var arr = [1,2,3,4,5];

  var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
  });
Gabriel Cheung
sumber
2
Ini berbeda - tidak menjalankan semua operasi secara paralel, tetapi menjalankannya secara berurutan.
Andrey Tserkus
5
@AndreyTserkus Promise.mapSeriesatau berurutan Promise.each, Promise.mapmemulai semuanya sekaligus.
Kiechlus
1
@AndreyTserkus Anda dapat menjalankan semua atau beberapa operasi secara paralel dengan memberikan concurrencyopsi.
11
Perlu disebutkan bahwa ini bukan vanilla JS.
Michal
@ Michael ya, itu SyntaxError
CS QGB
6

Jika Anda memetakan ke array Janji, Anda kemudian dapat menyelesaikannya semua ke array angka. Lihat Janji . Semua.

Dan Beaulieu
sumber
2

Saya akan merekomendasikan menggunakan Promise.all seperti yang disebutkan di atas, tetapi jika Anda benar-benar ingin menghindari pendekatan itu, Anda dapat melakukan for atau loop lainnya:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}
Beckster
sumber
6
Promise.all akan async untuk setiap elemen array. Ini akan menjadi sinkronisasi, harus menunggu untuk menyelesaikan satu elemen untuk memulai yang berikutnya.
Santiago Mendoza Ramirez
Bagi mereka yang mencoba pendekatan ini, perhatikan bahwa for..of adalah cara yang tepat untuk mengulangi isi array, sedangkan untuk..in beralih pada indeks.
ralfoide
2

Solusi di bawah ini untuk memproses semua elemen array secara tidak sinkron DAN mempertahankan pesanan:

const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));

const calc = async n => {
  await randomDelay();
  return n * 2;
};

const asyncFunc = async () => {
  const unresolvedPromises = arr.map(n => calc(n));
  const results = await Promise.all(unresolvedPromises);
};

asyncFunc();

Juga codepen .

Perhatikan kami hanya "menunggu" untuk Promise.all. Kami memanggil calc tanpa "menunggu" beberapa kali, dan kami segera mengumpulkan berbagai janji yang belum terselesaikan. Kemudian Promise.all menunggu resolusi semua dari mereka dan mengembalikan array dengan nilai yang diselesaikan secara berurutan.

Miki
sumber