Bagaimana saya bisa menggunakan async / menunggu di tingkat atas?

185

Saya telah membahas async/ awaitdan setelah mempelajari beberapa artikel, saya memutuskan untuk mengujinya sendiri. Namun, sepertinya saya tidak bisa memahami mengapa ini tidak berhasil:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

Konsol menampilkan yang berikut (simpul v8.6.0):

> luar: [Janji objek]

> di dalam: Hei di sana

Mengapa pesan log di dalam fungsi dijalankan setelah itu? Saya pikir alasan async/ awaitdiciptakan adalah untuk melakukan eksekusi sinkron menggunakan tugas asinkron.

Apakah ada cara saya bisa menggunakan nilai yang dikembalikan di dalam fungsi tanpa menggunakan .then()after main()?

Felipe
sumber
4
Tidak, hanya mesin waktu yang dapat membuat kode sinkron sinkron. awaittidak lain adalah gula untuk thensintaksis janji .
Bergi
Mengapa main mengembalikan nilai? Jika seharusnya, mungkin itu bukan titik masuk dan perlu dipanggil oleh fungsi lain (misalnya async IIFE).
Estus Flask
@estus itu hanya nama fungsi cepat ketika saya menguji hal-hal dalam node, belum tentu mewakili programmain
Felipe
2
FYI, async/awaitadalah bagian dari ES2017, bukan ES7 (ES2016)
Felix Kling

Jawaban:

270

Sepertinya saya tidak bisa membungkus kepala saya mengapa ini tidak berhasil.

Karena mainmengembalikan janji; semua asyncfungsi lakukan.

Di tingkat atas, Anda harus:

  1. Gunakan asyncfungsi tingkat atas yang tidak pernah menolak (kecuali jika Anda ingin kesalahan "penolakan ditangani"), atau

  2. Gunakan thendan catch, atau

  3. (Segera hadir!) Gunakan level-atasawait , proposal yang telah mencapai Tahap 3 dalam proses yang memungkinkan penggunaan level-atas awaitdalam sebuah modul.

# 1 - asyncFungsi tingkat atas yang tidak pernah ditolak

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

Perhatikan catch; Anda harus menangani penolakan janji / pengecualian async, karena tidak ada hal lain yang terjadi; Anda tidak memiliki penelepon untuk meneruskannya. Jika Anda mau, Anda bisa melakukan itu dengan memanggilnya melalui catchfungsi (bukan try/ catchsintaks):

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

... yang sedikit lebih ringkas (saya suka karena alasan itu).

Atau, tentu saja, jangan menangani kesalahan dan biarkan kesalahan "unhandled rejection".

# 2 - thendancatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

The catchhandler akan dipanggil jika terjadi kesalahan dalam rantai atau di Anda thenhandler. (Pastikan catchpawang Anda tidak melakukan kesalahan, karena tidak ada yang terdaftar untuk menanganinya.)

Atau keduanya argumen untuk then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

Sekali lagi perhatikan kami mendaftarkan penangan penolakan. Tetapi dalam formulir ini, pastikan bahwa kedua thencallback Anda tidak melakukan kesalahan, tidak ada yang terdaftar untuk menanganinya.

Level 3 teratas awaitdalam modul

Kamu tidak bisa menggunakan await di tingkat atas skrip non-modul, tetapi proposal tingkat atasawait ( Tahap 3 ) memungkinkan Anda untuk menggunakannya di tingkat atas modul. Ini mirip dengan menggunakan asyncpembungkus fungsi tingkat atas (# 1 di atas) karena Anda tidak ingin kode tingkat atas Anda ditolak (melempar kesalahan) karena itu akan menghasilkan kesalahan penolakan yang tidak tertangani. Jadi, kecuali jika Anda ingin memiliki penolakan yang tidak ditangani ketika ada masalah, seperti dengan # 1, Anda ingin membungkus kode Anda dalam penangan kesalahan:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

Perhatikan bahwa jika Anda melakukan ini, modul apa pun yang mengimpor dari modul Anda akan menunggu sampai janji Anda await selesai; ketika sebuah modul yang menggunakan tingkat atas awaitdievaluasi, pada dasarnya ia mengembalikan janji kepada pemuat modul (seperti halnya asyncfungsi), yang menunggu sampai janji itu diselesaikan sebelum mengevaluasi isi modul apa pun yang bergantung padanya.

TJ Crowder
sumber
Memikirkannya sebagai janji menjelaskan sekarang mengapa fungsinya segera kembali. Saya bereksperimen dengan membuat fungsi async anonim tingkat atas dan saya mendapatkan hasil yang masuk akal sekarang
Felipe
2
@Felipe: Ya, async/ awaityang sintaksis gula sekitar janji (baik jenis gula :-)). Anda tidak hanya menganggapnya sebagai mengembalikan janji; sebenarnya. ( Perincian .)
TJ Crowder
1
@LukeMcGregor - Saya menunjukkan keduanya di atas, dengan semua asyncopsi pertama. Untuk fungsi tingkat atas, saya bisa melihatnya dengan cara lain (kebanyakan karena dua tingkat lekukan pada asyncversi).
TJ Crowder
3
@Felipe - Saya telah memperbarui jawaban sekarang bahwa awaitproposal tingkat atas telah mencapai Tahap 3. :-)
TJ Crowder
1
@ SurajShrestha - Tidak. Tapi itu bukan masalah yang tidak terjadi. :-)
TJ Crowder
7

Tingkat Atasawait telah pindah ke tahap 3, jadi jawaban untuk pertanyaan Anda Bagaimana saya bisa menggunakan async / menunggu di tingkat atas? adalah dengan hanya menambahkan awaitpanggilan ke main():

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

Atau hanya:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

Perlu diingat bahwa itu masih hanya tersedia di [email protected] .

Jika Anda menggunakan TypeScript , itu mendarat di 3.8 .

v8 telah menambahkan dukungan dalam modul.

Ini juga didukung oleh Deno (seperti dikomentari oleh gonzalo-bahamondez).

Talas
sumber
Cukup keren. Apakah kami memiliki peta jalan untuk implementasi Node
Felipe
Tidak tahu, tapi kemungkinan besar kita akan melihat implementasi TypeScript dan Babel segera. Tim TypeScript memiliki kebijakan untuk mengimplementasikan fitur bahasa tahap-3, dan plugin Babel biasanya dibangun sebagai bagian dari proses TC39 untuk menguji proposal. Lihat github.com/Microsoft/TypeScript/issues/…
Taro
Ini juga tersedia dalam deno (hanya js, naskah masih tidak mendukungnya github.com/microsoft/TypeScript/issues/25988 ) deno.land lihat deno.news/issues/… .
Gonzalo Bahamondez
SyntaxError: await hanya valid dalam fungsi async
Sudipta Dhara
4

Solusi aktual untuk masalah ini adalah dengan mendekatinya secara berbeda.

Mungkin tujuan Anda adalah semacam inisialisasi yang biasanya terjadi di tingkat atas aplikasi.

Solusinya adalah untuk memastikan bahwa hanya ada satu pernyataan JavaScript tunggal di tingkat atas aplikasi Anda. Jika Anda hanya memiliki satu pernyataan di bagian atas aplikasi Anda, maka Anda bebas untuk menggunakan async / menunggu di setiap titik lain di mana saja (tunduk pada aturan sintaksis yang normal)

Dengan kata lain, bungkus seluruh tingkat teratas Anda dalam suatu fungsi sehingga tidak lagi tingkat atas dan yang memecahkan pertanyaan tentang bagaimana menjalankan async / menunggu di tingkat atas aplikasi - Anda tidak.

Seperti inilah tampilan level teratas aplikasi Anda:

import {application} from './server'

application();
Duke Dougal
sumber
1
Anda benar bahwa tujuan saya adalah inisialisasi. Hal-hal seperti koneksi basis data, data menarik dll. Dalam beberapa kasus perlu untuk mendapatkan data pengguna sebelum melanjutkan dengan sisa aplikasi. Pada dasarnya Anda mengusulkan itu application()menjadi async?
Felipe
1
Tidak, saya hanya mengatakan bahwa jika hanya ada satu pernyataan JavaScript di root aplikasi Anda maka masalah Anda hilang - pernyataan tingkat atas seperti yang ditunjukkan bukan async. Masalahnya adalah bahwa tidak mungkin untuk menggunakan async di tingkat atas - Anda tidak bisa menunggu untuk benar-benar menunggu di tingkat itu - karena itu jika hanya ada satu pernyataan di tingkat atas maka Anda telah mengesampingkan masalah itu. Inisialisasi kode async Anda sekarang turun di beberapa kode yang diimpor dan karena itu async akan berfungsi dengan baik, dan Anda dapat menginisialisasi semuanya pada awal aplikasi Anda.
Duke Dougal
1
KOREKSI - aplikasi IS fungsi async.
Duke Dougal
4
Saya tidak jelas maaf. Intinya adalah bahwa biasanya, di tingkat atas, fungsi async tidak menunggu .... JavaScript langsung menuju ke pernyataan berikutnya sehingga Anda tidak dapat memastikan bahwa kode init Anda telah selesai. Jika hanya ada satu pernyataan tunggal di bagian atas aplikasi Anda bahwa itu tidak masalah.
Duke Dougal
3

Untuk memberikan info lebih lanjut di atas jawaban saat ini:

Isi node.jsfile saat ini digabungkan, dengan cara seperti string, untuk membentuk fungsi tubuh.

Misalnya jika Anda memiliki file test.js:

// Amazing test file!
console.log('Test!');

Kemudian node.jsdiam-diam akan menggabungkan fungsi yang terlihat seperti:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

Hal utama yang perlu diperhatikan, adalah bahwa fungsi yang dihasilkan BUKAN fungsi async. Jadi, Anda tidak dapat menggunakan istilah itu awaitlangsung di dalamnya!

Tetapi katakan Anda perlu bekerja dengan janji-janji di file ini, maka ada dua metode yang mungkin:

  1. Jangan gunakan await langsung di dalam fungsi
  2. Jangan gunakan await

Opsi 1 mengharuskan kami untuk membuat ruang lingkup baru (dan ruang lingkup ini bisa async, karena kami memiliki kendali atas itu):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

Opsi 2 mengharuskan kita untuk menggunakan API janji berorientasi objek (paradigma yang kurang cantik namun fungsional untuk bekerja dengan janji)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

Saya pribadi berharap bahwa, jika bisa diterapkan, node.js akan secara default menggabungkan kode menjadi suatu asyncfungsi. Itu akan menghilangkan sakit kepala ini.

Gershom
sumber
0

Menunggu level atas adalah fitur standar EcmaScript yang akan datang. Saat ini, Anda dapat mulai menggunakannya dengan TypeScript 3.8 (dalam versi RC saat ini).

Cara Memasang TypeScript 3.8

Anda dapat mulai menggunakan TypeScript 3.8 dengan menginstalnya dari npm menggunakan perintah berikut:

$ npm install typescript@rc

Pada saat ini, Anda perlu menambahkan rctag untuk menginstal naskah versi 3.8 terbaru.

Ahmed Bouchefra
sumber
Tetapi Anda harus menjelaskan bagaimana cara menggunakannya?
raarts
-2

Karena main()berjalan secara tidak sinkron, ia mengembalikan janji. Anda harus mendapatkan hasilnya dalam then()metode. Dan karena then()mengembalikan janji juga, Anda harus menelepon process.exit()untuk mengakhiri program.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )
Peracek
sumber
2
Salah. Setelah semua janji telah diterima atau ditolak dan tidak ada lagi kode yang berjalan di utas utama, proses berakhir dengan sendirinya.
@ Ev: biasanya Anda ingin memberikan nilai yang berbeda untuk exit()memberi sinyal apakah terjadi kesalahan.
9000
@ 9000 Ya, tapi itu tidak dilakukan di sini, dan karena kode keluar 0 adalah default, tidak perlu untuk memasukkannya
@ 9000 sebenarnya, penangan kesalahan mungkin harus menggunakanprocess.exit(1)