typescript - Bagaimana cara mengembalikan Promise dalam fungsi async Javascript? Mengapa metode Async tidak membungkus Promise yang dikembalikan?


Saya memiliki kode berikut. Niat saya adalah memulai sebuah tugas (yang melibatkan beberapa panggilan await sebelum benar-benar bisa dimulai). Ketika tugas telah berhasil dimulai, saya perlu memperbarui UI untuk memberi tahu pengguna bahwa tugas telah dimulai dan sedang menunggu hasil. Hasil akan datang nanti, jadi saya ingin mengembalikan Promise lain agar aplikasi tahu kapan harus memperbarui UI dengan hasil yang dikembalikan:

async function startSomethingAsync() {
    await new Promise(r => globalThis.setTimeout(r, 1000));
    console.log("Task started");

    const finishedPromise = new Promise<number>(r => {
        globalThis.setTimeout(() => r(100), 3000);
    });
    return finishedPromise;
}

(async() => {
    // resultPromise is number instead of Promise<number>
    const resultPromise = await startSomethingAsync();

    // My intention: update UI after starting the task
    console.log("Task started");

    // Then when the result is there, do something else
    // Currently this is not compiled because resultPromise is not a Promise
    resultPromise.then(r => console.log("Result arrived: " + r));
})();

Namun kode di atas tidak bekerja. Entah bagaimana resultPromise adalah number (100 setelah diselesaikan) alih-alih Promise<number>. TypeScript juga mengenali startSomethingAsync mengembalikan Promise<number> alih-alih Promise<Promise<number>>.

Mengapa ini bisa terjadi? Bukankah metode async seharusnya membungkus Promise lain di luar Promise yang dikembalikan? Bagaimana saya bisa mencapai apa yang sedang saya coba lakukan dan apakah ini pola yang "baik"?


Saya bahkan mencoba membungkusnya sendiri:

async function startSomethingAsync() {
    await new Promise(r => globalThis.setTimeout(r, 1000));
    console.log("Task started");

    const finishedPromise = new Promise<Promise<number>>(r1 => r1(new Promise<number>(r2 => {
        globalThis.setTimeout(() => r2(100), 3000);
    })));
    return finishedPromise;
}

Fungsi tersebut masih mengembalikan Promise<number> alih-alih Promise<Promise<number>>.

Jawaban #1

Seperti yang kita bahas di komentar - kamu harus membungkus promise yang dihasilkan dalam sebuah fungsi atau objek.

async function startSomethingAsync() {
  await new Promise(r => setTimeout(r, 1000));

  const finishedPromise = new Promise(r => {
    setTimeout(() => r(100), 3000);
  });
  return () => finishedPromise;
}

(async () => {
  const resultPromise = await startSomethingAsync();
  resultPromise().then((r) => console.log("Result arrived: " + r));
})();

Jawaban #2

Mengapa ini terjadi? Bukankah metode async seharusnya membungkus Promise lain di luar Promise yang dikembalikan?

Tidak. Jika Anda mengembalikan sebuah promise dari fungsi async, promise yang dibuat dengan memanggil fungsi async itu hanya diselesaikan ke promise itu (ia menunggu promise lain itu selesai dan mengambil pemenuhannya atau penolakannya sebagai miliknya sendiri; lebih banyak tentang terminologi promise di blog saya di sini). Itu tidak mengatur promise agar dipenuhi dengan sebuah promise. Sebuah promise tidak pernah dipenuhi dengan sebuah promise (terlepas dari fungsi async).

Bagaimana saya mencapai apa yang saya coba lakukan dan apakah ini pola yang "baik"?

Jika Anda ingin menggunakan promise dari startSomethingAsync, cukup jangan await itu:

(async() => {
    const resultPromise = startSomethingAsync();
    //                    ^−−− no `await`

    // My intention: update UI after starting the task
    console.log("Task started");

    // Wait until it's finished
    const r = await resultPromise;
    //        ^−−− *now* we `await` await it
    console.log("Result arrived: " + r);
})();

Perlu dicatat bahwa jika startSomethingAsync memiliki penundaan asinkron sebelum mulai mengerjakan tugasnya (yang akan sangat tidak biasa), log "Task started" akan muncul sebelum penundaan asinkron itu terjadi. Jika Anda tidak menginginkannya, pisahkan bagian tersebut dari fungsi sehingga Anda dapat memperoleh log "Task started" di tengah. Misalnya, Anda dapat memiliki fungsi terpisah yang melakukan bagian awal, lalu Anda mem-passing apapun dari itu ke startSomethingAsync:

(async() => {
    const resultPromise = startSomethingAsync(await theStuffBefore());
    //                                        ^^^^^

    // My intention: update UI after starting the task
    console.log("Task started");

    // Wait until it's finished
    const r = await resultPromise;
    //        ^−−− *now* we `await` await it
    console.log("Result arrived: " + r);
})();

Sebagai alternatif, jika proses Anda terdiri dari beberapa langkah, setiap langkah dapat mengembalikan sebuah fungsi (atau objek dengan metode) untuk langkah berikutnya:

(async() => {
    const nextStep = await startSomethingAsync();
    const resultPromise = nextStep(); // No `await`

    // My intention: update UI after starting the task
    console.log("Task started");

    // Wait until it's finished
    const r = await resultPromise;
    console.log("Result arrived: " + r);
})();

(Sebenarnya, itu sangat mirip dengan menggunakan generator async.)

Ini semua adalah variasi dari sebuah tema, tetapi pesan intinya adalah bahwa sebuah janji tidak pernah bisa dipenuhi dengan janji lain. Anda akan memperhatikan bahwa nama standar untuk fungsi yang Anda dapatkan dari new Promise adalah resolve dan reject, bukan fulfill dan reject. Itu karena ketika Anda memberikan sebuah janji ke resolve (atau mengembalikannya dari sebuah fungsi async, yang pada dasarnya adalah hal yang sama), itu menyelesaikan janji tersebut kepada janji yang lain, bukan memenuhi janji tersebut dengan janji yang lain.

Artikel terkait