Parsing Async / Menunggu dalam JavaScript dengan Contoh



Penulis artikel mem-parsing Async / Menunggu dalam JavaScript menggunakan contoh-contoh. Secara umum, Async / Menunggu adalah cara yang nyaman untuk menulis kode asinkron. Sebelum kesempatan ini, kode serupa ditulis menggunakan callback dan janji. Penulis artikel asli mengungkapkan manfaat Async / Menunggu dengan memeriksa berbagai contoh.

Kami mengingatkan Anda: untuk semua pembaca "Habr" - diskon 10.000 rubel saat mendaftar untuk kursus Skillbox menggunakan kode promo "Habr".

Rekomendasi Skillbox: Kursus Pendidikan Online Pengembang Java .

Telepon balik


Callback adalah fungsi yang panggilannya tertunda tanpa batas waktu. Sebelumnya, callback digunakan di bagian-bagian kode yang hasilnya tidak dapat diperoleh dengan segera.

Berikut adalah contoh membaca file di Node.js secara tidak sinkron:

fs.readFile(__filename, 'utf-8', (err, data) => { if (err) { throw err; } console.log(data); }); 

Masalah muncul ketika Anda perlu melakukan beberapa operasi asinkron sekaligus. Mari kita bayangkan skenario ini: permintaan dibuat ke database pengguna Arfat, Anda perlu membaca bidang profile_img_url dan mengunduh gambar dari server someserver.com.
Setelah mengunduh, konversikan gambar ke format lain, misalnya, dari PNG ke JPEG. Jika konversi berhasil, email dikirim ke email pengguna. Selanjutnya, informasi tentang acara dimasukkan dalam file transformations.log dengan tanggal.



Layak untuk memperhatikan pengenaan panggilan balik dan sejumlah besar}) di bagian akhir kode. Ini disebut Callback Hell atau Pyramid of Doom.

Kerugian dari metode ini jelas:

  • Kode ini sulit dibaca.
  • Juga sulit untuk menangani kesalahan di dalamnya, yang sering mengarah pada penurunan kualitas kode.

Untuk mengatasi masalah ini, janji ditambahkan ke JavaScript. Mereka memungkinkan Anda untuk mengganti sarang panggilan balik yang dalam dengan kata .then.



Poin positif dari janji-janji itu adalah bahwa dengan mereka kode dibaca jauh lebih baik, dari atas ke bawah, dan bukan dari kiri ke kanan. Namun demikian, janji juga memiliki masalah:

  • Perlu menambahkan sejumlah besar. Maka
  • Alih-alih mencoba / menangkap, .catch digunakan untuk menangani semua kesalahan.
  • Bekerja dengan beberapa janji dalam satu siklus masih jauh dari nyaman, dalam beberapa kasus, mereka memperumit kode.

Berikut adalah tugas yang akan menunjukkan arti paragraf terakhir.

Misalkan ada loop for yang mencetak urutan angka dari 0 hingga 10 dengan interval acak (0 - n detik). Dengan menggunakan janji, Anda harus mengubah siklus ini sehingga angka ditampilkan dalam urutan dari 0 hingga 10. Jadi, jika output nol membutuhkan waktu 6 detik dan unit membutuhkan waktu 2 detik, nol harus menjadi output pertama, dan kemudian hitung mundur output unit akan dimulai.

Dan tentu saja, untuk mengatasi masalah ini, kami tidak menggunakan Async / Menunggu atau .sort. Contoh solusi ada di bagian akhir.

Fungsi Async


Menambahkan fungsi async ke ES2017 (ES8) telah menyederhanakan tugas bekerja dengan janji-janji. Saya perhatikan bahwa fungsi async bekerja di atas semua janji. Fungsi-fungsi ini tidak mewakili konsep yang berbeda secara kualitatif. Fungsi Async disusun sebagai alternatif untuk kode yang menggunakan janji.

Async / Await memungkinkan untuk mengatur kerja dengan kode asinkron dalam gaya sinkron.

Dengan demikian, pengetahuan tentang janji memudahkan untuk memahami prinsip-prinsip Async / Menunggu.

Sintaks

Dalam situasi tertentu, ini terdiri dari dua kata kunci: async dan menunggu. Kata pertama membuat fungsi tidak sinkron. Fungsi-fungsi ini memungkinkan menunggu. Dalam kasus lain, menggunakan fungsi ini akan menyebabkan kesalahan.

 // With function declaration async function myFn() { // await ... } // With arrow function const myFn = async () => { // await ... } function myFn() { // await fn(); (Syntax Error since no async) } 

Async dimasukkan di bagian paling awal deklarasi fungsi, dan dalam kasus fungsi panah, antara tanda "=" dan tanda kurung.

Fungsi-fungsi ini dapat ditempatkan di objek sebagai metode atau digunakan dalam deklarasi kelas.

 // As an object's method const obj = { async getName() { return fetch('https://www.example.com'); } } // In a class class Obj { async getResource() { return fetch('https://www.example.com'); } } 

NB! Perlu diingat bahwa konstruktor dan getter / setter kelas tidak dapat tidak sinkron.

Semantik dan aturan eksekusi

Fungsi Async pada dasarnya mirip dengan fungsi JS standar, tetapi ada pengecualian.

Jadi, fungsi async selalu mengembalikan janji:

 async function fn() { return 'hello'; } fn().then(console.log) // hello 

Secara khusus, fn mengembalikan string halo. Nah, karena ini adalah fungsi asinkron, nilai string dibungkus dalam janji menggunakan konstruktor.

Berikut ini adalah desain alternatif tanpa Async:

 function fn() { return Promise.resolve('hello'); } fn().then(console.log); // hello 

Dalam hal ini, pengembalian janji dibuat "secara manual". Fungsi asinkron selalu membungkus dirinya dalam janji baru.

Jika nilai pengembaliannya primitif, fungsi async mengembalikan nilai, membungkusnya dengan janji. Jika nilai pengembalian adalah objek janji, solusinya dikembalikan dalam janji baru.

 const p = Promise.resolve('hello') p instanceof Promise; // true Promise.resolve(p) === p; // true 

Tetapi apa yang terjadi jika kesalahan terjadi di dalam fungsi asinkron?

 async function foo() { throw Error('bar'); } foo().catch(console.log); 

Jika tidak diproses, foo () akan mengembalikan janji dengan reduksi. Dalam situasi ini, alih-alih Promise.resolve, Promise.reject akan kembali berisi kesalahan.

Fungsi Async pada output selalu memberikan janji, terlepas dari apa yang dikembalikan.

Fungsi asinkron dijeda pada setiap menunggu.

Menunggu memengaruhi ekspresi. Jadi, jika ekspresi adalah janji, fungsi async ditangguhkan hingga janji dijalankan. Dalam hal ungkapan itu bukan janji, itu dikonversi menjadi janji melalui Janji. Selesaikan dan kemudian diakhiri.

 // utility function to cause delay // and get random value const delayAndGetRandom = (ms) => { return new Promise(resolve => setTimeout( () => { const val = Math.trunc(Math.random() * 100); resolve(val); }, ms )); }; async function fn() { const a = await 9; const b = await delayAndGetRandom(1000); const c = await 5; await delayAndGetRandom(1000); return a + b * c; } // Execute fn fn().then(console.log); 

Berikut ini deskripsi cara kerja fungsi fn.

  • Setelah memanggilnya, baris pertama dikonversi dari const a = wait 9; dalam const a = wait Promise.resolve (9);
  • Setelah menggunakan Tunggu, eksekusi fungsi ditangguhkan hingga menerima nilainya (dalam situasi saat ini, adalah 9).
  • delayAndGetRandom (1000) menjeda eksekusi fungsi fn sampai selesai sendiri (setelah 1 detik). Ini sebenarnya menghentikan fungsi fn selama 1 detik.
  • delayAndGetRandom (1000) melalui resolus mengembalikan nilai acak, yang kemudian ditugaskan ke variabel b.
  • Nah, kasus variabel c mirip dengan kasus variabel a. Setelah itu, semuanya berhenti sebentar, tetapi sekarang delayAndGetRandom (1000) tidak mengembalikan apa-apa, karena ini tidak diperlukan.
  • Akibatnya, nilai-nilai dihitung dengan rumus a + b * c. Hasilnya dibungkus dengan janji menggunakan Promise.resolve dan dikembalikan oleh fungsi.

Jeda ini mungkin menyerupai generator di ES6, tetapi ada alasan untuk ini .

Kami memecahkan masalah


Nah, sekarang mari kita lihat solusi untuk masalah yang disebutkan di atas.



Fungsi finishMyTask menggunakan Menunggu untuk menunggu hasil operasi seperti queryDatabase, sendEmail, logTaskInFile, dan lainnya. Jika kita membandingkan keputusan ini dengan di mana janji-janji itu digunakan, persamaannya akan menjadi jelas. Namun demikian, versi dengan Async / Await sangat menyederhanakan semua kesulitan sintaksis. Dalam hal ini, tidak ada banyak panggilan balik dan rantai seperti .then / .catch.

Berikut ini adalah solusi dengan output angka, ada dua opsi.

 const wait = (i, ms) => new Promise(resolve => setTimeout(() => resolve(i), ms)); // Implementation One (Using for-loop) const printNumbers = () => new Promise((resolve) => { let pr = Promise.resolve(0); for (let i = 1; i <= 10; i += 1) { pr = pr.then((val) => { console.log(val); return wait(i, Math.random() * 1000); }); } resolve(pr); }); // Implementation Two (Using Recursion) const printNumbersRecursive = () => { return Promise.resolve(0).then(function processNextPromise(i) { if (i === 10) { return undefined; } return wait(i, Math.random() * 1000).then((val) => { console.log(val); return processNextPromise(i + 1); }); }); }; 

Dan di sini ada solusi menggunakan fungsi async.

 async function printNumbersUsingAsync() { for (let i = 0; i < 10; i++) { await wait(i, Math.random() * 1000); console.log(i); } } 

Menangani kesalahan

Kesalahan yang belum diproses dibungkus dengan janji yang ditolak. Namun, dalam fungsi async, Anda dapat menggunakan konstruksi coba / tangkap untuk melakukan penanganan kesalahan sinkron.

 async function canRejectOrReturn() { // wait one second await new Promise(res => setTimeout(res, 1000)); // Reject with ~50% probability if (Math.random() > 0.5) { throw new Error('Sorry, number too big.') } return 'perfect number'; } 

canRejectOrReturn () adalah fungsi asinkron yang berhasil ("angka sempurna") atau gagal dengan kesalahan ("Maaf, angka terlalu besar").

 async function foo() { try { await canRejectOrReturn(); } catch (e) { return 'error caught'; } } 

Karena canRejectOrReturn diharapkan dieksekusi dalam contoh di atas, penghentiannya yang tidak berhasil akan memerlukan eksekusi blok tangkap. Akibatnya, fungsi foo akan berakhir dengan tidak terdefinisi (ketika tidak ada yang dikembalikan di blok coba) atau dengan kesalahan tertangkap. Akibatnya, fungsi ini tidak akan gagal, karena try / catch akan menangani fungsi foo itu sendiri.

Ini adalah contoh lain:

 async function foo() { try { return canRejectOrReturn(); } catch (e) { return 'error caught'; } } 

Perlu memperhatikan fakta bahwa dalam contoh dari foo canRejectOrReturn dikembalikan. Foo dalam hal ini dilengkapi dengan angka sempurna atau mengembalikan kesalahan Kesalahan ("Maaf, angka terlalu besar"). Blok tangkapan tidak akan pernah dieksekusi.

Masalahnya adalah bahwa foo mengembalikan janji yang diteruskan dari canRejectOrReturn. Oleh karena itu, solusi untuk fungsi foo menjadi solusi untuk canRejectOrReturn. Dalam hal ini, kode hanya terdiri dari dua baris:

 try { const promise = canRejectOrReturn(); return promise; } 

Tetapi apa yang terjadi jika Anda menggunakan menunggu dan kembali bersama:

 async function foo() { try { return await canRejectOrReturn(); } catch (e) { return 'error caught'; } } 

Dalam kode di atas, foo berhasil dengan angka sempurna dan kesalahan ditangkap. Tidak akan ada kegagalan. Tapi foo akan diakhiri dengan canRejectOrReturn, dan bukan dengan undefined. Mari kita pastikan ini dengan menghapus kembali menunggu canRejectOrReturn () baris:

 try { const value = await canRejectOrReturn(); return value; } // … 

Kesalahan dan Kesalahan Umum


Dalam beberapa kasus, menggunakan Async / Await dapat menyebabkan kesalahan.

Lupa menunggu

Ini cukup sering terjadi - sebelum janji, kata kunci tunggu dilupakan:

 async function foo() { try { canRejectOrReturn(); } catch (e) { return 'caught'; } } 

Dalam kode, seperti yang Anda lihat, tidak ada menunggu atau kembali. Karena itu, foo selalu keluar dengan tidak terdefinisi tanpa penundaan selama 1 detik. Tapi janji itu akan dipenuhi. Jika memberikan kesalahan atau reduksi, maka UnhandledPromiseRejectionWarning akan dipanggil.

Fungsi Async di Callback

Fungsi async sering digunakan dalam .map atau .filter sebagai panggilan balik. Contohnya adalah fungsi fetchPublicReposCount (nama pengguna), yang mengembalikan jumlah repositori yang terbuka di GitHub. Katakanlah ada tiga pengguna yang metriknya kita butuhkan. Berikut adalah kode untuk tugas ini:

 const url = 'https://api.github.com/users'; // Utility fn to fetch repo counts const fetchPublicReposCount = async (username) => { const response = await fetch(`${url}/${username}`); const json = await response.json(); return json['public_repos']; } 

Kami membutuhkan akun ArfatSalman, octocat, norvig. Dalam hal ini, jalankan:

 const users = [ 'ArfatSalman', 'octocat', 'norvig' ]; const counts = users.map(async username => { const count = await fetchPublicReposCount(username); return count; }); 

Anda harus memperhatikan Menunggu dalam panggilan balik .map. Di sini jumlah adalah serangkaian janji, well .map adalah panggilan balik anonim untuk setiap pengguna yang ditentukan.

Penggunaan menunggu yang sangat konsisten

Ambil kode berikut sebagai contoh:

 async function fetchAllCounts(users) { const counts = []; for (let i = 0; i < users.length; i++) { const username = users[i]; const count = await fetchPublicReposCount(username); counts.push(count); } return counts; } 

Di sini, nomor repo ditempatkan dalam variabel jumlah, kemudian nomor ini ditambahkan ke array jumlah. Masalah dengan kode adalah bahwa sampai data pengguna pertama tiba dari server, semua pengguna berikutnya akan berada dalam mode siaga. Dengan demikian, dalam satu saat, hanya satu pengguna yang diproses.

Jika, misalnya, dibutuhkan sekitar 300 ms untuk memproses satu pengguna, maka untuk semua pengguna ini sudah satu detik, waktu yang dihabiskan secara linear tergantung pada jumlah pengguna. Tetapi karena mendapatkan jumlah repo tidak bergantung satu sama lain, prosesnya dapat diparalelkan. Ini membutuhkan kerja dengan .map dan Promise.all:

 async function fetchAllCounts(users) { const promises = users.map(async username => { const count = await fetchPublicReposCount(username); return count; }); return Promise.all(promises); } 

Promise.all pada input menerima berbagai janji dengan kembalinya janji. Yang terakhir setelah menyelesaikan semua janji dalam array atau pada reduksi pertama selesai. Mungkin saja mereka semua tidak memulai pada saat yang sama - untuk memastikan peluncuran secara bersamaan, Anda dapat menggunakan p-peta.

Kesimpulan


Fitur Async menjadi semakin penting untuk pengembangan. Nah, untuk penggunaan fungsi-fungsi async yang adaptif, ada baiknya menggunakan Async Iterators . Pengembang JavaScript harus berpengalaman dalam hal ini.

Skillbox merekomendasikan:

Source: https://habr.com/ru/post/id458950/


All Articles