A Primer ES7 Async fungsi
Indonesian (Bahasa Indonesia) translation by Dendi Deden (you can also view the original English article)
Jika Anda sudah mengikuti dunia JavaScript, Anda mungkin pernah mendengar tentang Promise. Ada beberapa tutorial yang besar secara online jika Anda ingin belajar tentang Promise, tapi aku tidak akan menjelaskan mereka di sini; Artikel ini mengasumsikan Anda telah memiliki pengetahuan tentang Promise.
Promise yang disebut-sebut sebagai masa depan pemrograman JavaScript asynchronous. Promise benar-benar bagus dan membantu memecahkan banyak masalah yang timbul dengan pemrograman asynchronous, tetapi yang mengklaim hanya agak benar. Pada kenyataannya, pronise adalah dasar dari masa depan pemrograman JavaScript asynchronous. Idealnya, Promise akan terselip di balik layar dan kami akan dapat menulis kode asynchronous kita seolah-olah synchronous.
di ECMAScript 7, ini akan menjadi lebih dari mimpi aneh: itu akan menjadi kenyataan, dan aku akan menunjukkan kepada Anda bahwa realitas — disebut async fungsi — sekarang. Mengapa kita berbicara tentang ini sekarang? Setelah semua, ES6 belum bahkan benar-benar selesai, jadi siapa yang tahu berapa lama itu akan sebelum kita melihat ES7. Kebenaran adalah Anda dapat menggunakan teknologi ini sekarang, dan pada akhir posting ini, saya akan menunjukkan Anda bagaimana.
Keadaan saat ini
Sebelum saya mulai menunjukkan cara menggunakan fungsi async, saya ingin pergi melalui beberapa contoh dengan Promise (menggunakan Promse ES6). Kemudian, saya akan mengkonversi contoh-contoh ini menggunakan fungsi async sehingga Anda dapat melihat perbedaan besar yang dibuat.
Contoh
Untuk contoh pertama kami, kami akan melakukan sesuatu yang benar-benar sederhana: memanggil fungsi yang asynchronous dan logging nilai kembalian.
function getValues() { return Promise.resolve([1,2,3,4]); } getValues().then(function(values) { console.log(values); });
Sekarang bahwa kita memiliki contoh dasar didefinisikan, mari kita melompat menjadi sesuatu yang sedikit lebih rumit. Aku akan menggunakan dan memodifikasi contoh dari posting di blog saya sendiri yang pergi melalui beberapa pola untuk menggunakan promise dalam skenario yang berbeda. Setiap contoh asynchronously mengambil sebuah array nilai, melakukan operasi asynchronous yang berubah setiap nilai dalam array, log setiap nilai baru dan akhirnya mengembalikan array penuh dengan nilai-nilai baru.
Pertama, kita akan melihat contoh yang akan menjalankan beberapa operasi asynchronous secara paralel, dan kemudian menanggapi mereka segera sebagai masing selesai, terlepas dari urutan di mana mereka selesai. getValues
fungsi yang sama dari contoh sebelumnya. fungsi asyncOperation
juga digunakan kembali dalam contoh-contoh yang akan datang.
function asyncOperation(value) { return Promise.resolve(value + 1); } function foo() { return getValues().then(function(values) { var operations = values.map(function(value) { return asyncOperation(value).then(function(newValue) { console.log(newValue); return newValue; }); }); return Promise.all(operations); }).catch(function(err) { console.log('We had an ', err); }); }
Kita dapat melakukan hal yang sama persis, tapi pastikan logging yang terjadi dalam urutan elemen dalam array. Dengan kata lain, contoh ini berikutnya akan melakukan bekerja asynchronous secara paralel, tapi pekerjaan synchronous akan berurutan:
function foo() { return getValues().then(function(values) { var operations = values.map(asyncOperation); return Promise.all(operations).then(function(newValues) { newValues.forEach(function(newValue) { console.log(newValue); }); return newValues; }); }).catch(function(err) { console.log('We had an ', err); }); }
Contoh terakhir akan menunjukkan pola yang mana kita menunggu operasi asynchronous sebelumnya untuk menyelesaikan sebelum memulai berikutnya. Tidak ada yang berjalan secara paralel dalam contoh ini; segala sesuatu berurutan.
function foo() { var newValues = []; return getValues().then(function(values) { return values.reduce(function(previousOperation, value) { return previousOperation.then(function() { return asyncOperation(value); }).then(function(newValue) { console.log(newValue); newValues.push(newValue); }); }, Promise.resolve()).then(function() { return newValues; }); }).catch(function(err) { console.log('We had an ', err); }); }
Bahkan dengan kemampuan Promise untuk mengurangi callback nesting, itu benar-benar tidak membantu banyak. Menjalankan sejumlah pemanggilan asynchronous berurutan akan berantakan tidak peduli apa yang Anda lakukan. Hal ini terutama mengerikan untuk melihat semua kata kunci return
bersarang. Jika kami memasukan array newValues
melalui promise dalam reduce
callback daripada membuat global untuk fungsi seluruh foo
, kita harus menyesuaikan kode untuk memiliki kembalian bahkan lebih bersarang, seperti ini:
function foo() { return getValues().then(function(values) { return values.reduce(function(previousOperation, value) { return previousOperation.then(function(newValues) { return asyncOperation(value).then(function(newValue) { console.log(newValue); newValues.push(newValue); return newValues; }); }); }, Promise.resolve([])); }).catch(function(err) { console.log('We had an ', err); }); }
Jangan Anda setuju kita perlu untuk memperbaiki hal ini? Mari kita lihat solusi.
Async fungsi datang untuk menyelamatkan
Bahkan dengan promise, pemrograman asynchronous tidak persis sederhana dan tidak selalu beraliran baik dari A sampai Z. Synchronous pemrograman jauh lebih sederhana dan menulis dan membaca jauh lebih alami. Spesifikasi Async fungsi terlihat menjadi sarana (menggunakan ES6 generator di belakang layar) untuk menulis kode Anda seolah-Synchronous .
Bagaimana kita menggunakan mereka?
Hal pertama yang perlu kita lakukan adalah awalan fungsi kita dengan kata kunci async
. Tanpa kata kunci ini di tempat, kita tidak dapat menggunakan semua await
kata kunci di dalam fungsi, yang saya akan menjelaskannya sedikit.
Kata kunci async
tidak hanya memungkinkan kita untuk menggunakan await
, itu juga memastikan bahwa fungsi akan mengembalikan sebuah objek Promise
. Dalam fungsi async, setiap kali Anda mengembalikan
nilai, fungsi akan benar-benar kembali Promise
yang diselesaikan dengan nilai tersebut. Cara menolak adalah untuk melempar kesalahan, di mana kasus nilai penolakan akan objek kesalahan. Berikut adalah contoh sederhana:
async function foo() { if( Math.round(Math.random()) ) return 'Success!'; else throw 'Failure!'; } // Is equivalent to... function foo() { if( Math.round(Math.random()) ) return Promise.resolve('Success!'); else return Promise.reject('Failure!'); }
Kami bahkan belum sampai bagian terbaik dan kami sudah membuat kode kita lebih seperti sinkron kode karena kami tidak mampu menghentikan secara eksplisit main-main dengan objek Promise
. Kita dapat mengambil fungsi apapun dan membuatnya mengembalikan sebuah objek Promise
hanya dengan menambahkan kata kunci async
ke depan itu.
Mari kita pergi ke depan dan mengkonversi getValues
dan asyncOperation
fungsi:
async function getValues() { return [1,2,3,4]; } async function asyncOperation(value) { return value + 1; }
Mudah! Sekarang, mari kita lihat di bagian terbaik dari semua: await
kata kunci. Dalam fungsi async Anda, setiap kali Anda melakukan suatu operasi yang mengembalikan promise, Anda dapat throw await
kata kunci di depannya, dan itu akan menghentikan sisa fungsi sampai promise dikembalikan telah diselesaikan atau atau ditolak.. Pada saat itu, await promisingOperation()
akan mengevaluasi nilai diselesaikan atau ditolak. Sebagai contoh:
function promisingOperation() { return new Promise(function(resolve, reject) { setTimeout(function() { if( Math.round(Math.random()) ) resolve('Success!'); else reject('Failure!'); }, 1000); } } async function foo() { var message = await promisingOperation(); console.log(message); }
Ketika Anda memanggil foo
, itu akan menunggu sampai menyelesaikan promisingOperation
dan kemudian itu akan logout "Success!" pesan, atau promisingOperation
akan ditolak, dalam hal penolakan akan dimasukan dan foo
akan menolak dengan "Failure!". Karena foo tidak mengembalikan apa-apa, itu akan menyelesaikan dengan asumsi undefined
promisingOperation
sukses.
Ada hanya satu pertanyaan yang tersisa: bagaimana kita menyelesaikan kegagalan? Jawaban untuk pertanyaan itu sederhana: semua yang perlu kita lakukan adalah dibungkus dalam try... catch
blok. Jika salah satu operasi asynchronous ditolak, kita dapat menangkap
itu dan menanganinya:
async function foo() { try { var message = await promisingOperation(); console.log(message); } catch (e) { console.log('We failed:', e); } }
Sekarang bahwa kita telah menekan pada semua dasar-dasar, mari kita pergi melalui contoh promise kami sebelumnya dan mengkonversi mereka untuk menggunakan fungsi async.
Contoh
Contoh pertama di atas dibuat getValues
dan menggunakannya. Kami telah kembali membuat getValues
sehingga kita hanya perlu membuat ulang kode untuk menggunakannya. Ada satu peringatan yang potensial untuk async fungsi yang muncul di sini: kode akan dibutuhkan dalam fungsi. Contoh sebelumnya adalah di global scope (sejauh yang siapa pun bisa tahu), tetapi kita perlu untuk membungkus kode async kami dalam fungsi async untuk mendapatkannya bekerja:
async function() { console.log(await getValues()); }(); // The extra "()" runs the function immediately
Bahkan dengan pembungkus kode dalam fungsi, aku masih mengklaim lebih mudah dibaca dan memiliki lebih sedikit byte (jika Anda menghapus komentar). Contoh kami berikutnya, jika Anda ingat, melakukan segala sesuatu secara paralel. Ini sedikit rumit, karena kami memiliki inner fungsi yang perlu mengembalikan promise. Jika kita menggunakan kata kunci await
dalam inner fungsi, fungsi yang juga perlu diawali dengan async
.
async function foo() { try { var values = await getValues(); var newValues = values.map(async function(value) { var newValue = await asyncOperation(value); console.log(newValue); return newValue; }); return await* newValues; } catch (err) { console.log('We had an ', err); } }
Anda mungkin telah memperhatikan asterisk terpasang await
kata kunci terakhir. Hal ini tampaknya masih diperdebatkan sedikit, tapi kelihatannya seperti await*
akan pada dasarnya auto-wrap ekspresi untuk berdiri di Promise.all
. Sekarang, walaupun, tool kita akan melihat di kemudian tidak mendukung await*
, jadi itu harus dikonversi ke await Promise.all(newValues);
seperti yang kita lakukan dalam contoh berikutnya.
Contoh berikutnya akan menjalankan panggilan asyncOperation
secara paralel, tapi akan kemudian membawa itu semua kembali bersama-sama dan melakukan output secara berurutan.
async function foo() { try { var values = await getValues(); var newValues = await Promise.all(values.map(asyncOperation)); newValues.forEach(function(value) { console.log(value); }); return newValues; } catch (err) { console.log('We had an ', err); } }
Saya suka itu. Itulah sangat bersih. Jika kita menghapus await
dan async
kata kunci, menghapus Promise.all
wrapper, dan membuat getValues
dan asyncOperation
synchronous, maka kode ini akan masih bekerja dengan cara yang sama persis kecuali bahwa itu akan synchronous. Ini pada dasarnya apa yang kami bertujuan untuk dicapai.
Contoh terakhir akan, tentu saja, memiliki segalanya berjalan secara berurutan. Tidak ada operasi asynchronous dilakukan sampai sebelumnya selesai.
async function foo() { try { var values = await getValues(); return await values.reduce(async function(values, value) { values = await values; value = await asyncOperation(value); console.log(value); values.push(value); return values; }, []); } catch (err) { console.log('We had an ', err); } }
Sekali lagi, kami membuat async
inner fungsi. Ada quirk menarik yang terungkap dalam kode ini. Saya memasukan []
di sebagai "memo" nilai untuk reduce
, tapi kemudian saya menggunakan await
di atasnya. Nilai await
tidak diperlukan untuk promise. Ini dapat mengambil nilai apapun, dan jika tidak promise, itu tidak akan menunggu untuk itu; itu akan hanya menjalankan serentak. Tentu saja, meskipun, setelah pertama pelaksanaan panggilan balik, kami akan benar-benar bekerja dengan promise.
Contoh ini cukup banyak persis seperti contoh pertama, kecuali bahwa kita menggunakan reduce
daripada map
sehingga kita dapat await
operasi sebelumnya, dan kemudian karena kami menggunakan reduce
untuk membangun sebuah array (bukan sesuatu yang Anda biasanya lakukan, terutama jika Anda sedang membangun berbagai ukuran yang sama sebagai array original), kita perlu membangun array dalam callback untuk reduce
.
Menggunakan fungsi Async hari ini
Sekarang bahwa Anda mendapatkan sekilas tentang kesederhanaan dan keangkeran async fungsi, Anda mungkin menangis seperti saya lakukan pertama kali aku melihat mereka. Aku tidak menangis keluar dari sukacita (meskipun saya hampir melakukan); tidak, saya menangis karena ES7 tidak akan di sini sampai aku mati! Pada setidaknya itulah yang saya rasakan. Kemudian saya menemukan tentang Traceur.
Traceur ini ditulis dan dikelola oleh Google. Ini adalah transpiler yang mengubah kode ES6 untuk ES5. Itu tidak membantu! Yah, itu tidak, kecuali mereka juga menerapkan dukungan untuk async fungsi. Hal ini masih fitur percobaan, yang berarti Anda akan perlu memberitahukan secara eksplisit kompilator Anda menggunakan fitur itu, dan bahwa Anda pasti ingin menguji kode Anda secara menyeluruh untuk memastikan tidak ada masalah dengan kompilasi.
Menggunakan compiler seperti Traceur berarti bahwa Anda akan memiliki beberapa sedikit bengkak, jelek kode yang dikirim ke klien, yang tidak apa yang Anda inginkan, tetapi jika Anda menggunakan sumber maps, ini pada dasarnya menghilangkan sebagian besar kerugian yang terkait dengan pengembangan. Anda akan membaca, menulis, dan debugging bersih ES6/7 kode, daripada harus membaca, menulis, dan debug kekacauan rumit dari kode yang perlu bekerja di sekitar keterbatasan bahasa.
Tentu saja, ukuran kode masih akan lebih besar daripada jika Anda telah menuis tangan kode ES5 (kemungkinan besar), jadi Anda mungkin perlu untuk menemukan beberapa jenis keseimbangan antara kode maintainable dan performant kode, tapi itu adalah keseimbangan Anda sering perlu untuk menemukan bahkan tanpa menggunakan transpiler.
Menggunakan Traceur
Traceur adalah sebuah utilitas command line yang dapat diinstal melalui NPM:
npm install -g traceur
Secara umum, Traceur ini cukup mudah digunakan, tetapi beberapa pilihan yang dapat membingungkan dan mungkin memerlukan beberapa eksperimen. Anda dapat melihat daftar pilihan untuk rincian lebih lanjut. Yang kita benar-benar tertarik adalah opsi --experimental
.
Anda perlu gunakan pilihan ini untuk mengaktifkan fitur eksperimental, yang adalah bagaimana kita bisa async fungsi untuk bekerja. Setelah Anda memiliki file JavaScript (main.js
dalam kasus ini) dengan ES6 kode dan async fungsi termasuk, Anda hanya dapat kompilasi dengan ini:
traceur main.js --experimental --out compiled.js
Anda juga hanya dapat menjalankan kode dengan menghilangkan --out compiled.js
. Anda tidak akan melihat banyak kecuali kode memiliki pernyataan console.log
(atau output konsol lain), tapi setidaknya, Anda dapat memeriksa kesalahan. Anda mungkin ingin menjalankan itu dalam browser, meskipun. Jika ini terjadi, ada beberapa langkah Anda perlu untuk diambil.
- Download script
traceur-runtime.js
. Ada banyak cara untuk mendapatkannya, tapi salah satu yang paling mudah adalah dari NPM:npm install traceur-runtime
. File tersebut kemudian akan tersedia sebagaiindex.js
dalam folder modul tersebut. - Dalam file HTML Anda, menambahkan tag
script
yang menarik dalam Traceur Runtime Script. - Menambahkan tag
script
lain di bawah Traceur Runtime Script untuk menarikcompiled.js
.
Setelah ini, kode Anda harusnya berjalan!
Mengotomatisasi Traceur kompilasi
Selain hanya menggunakan tool command line Traceur, Anda juga dapat mengotomatisasi kompilasi sehingga Anda tidak perlu terus kembali ke konsol Anda dan kembali menjalankan kompilator. Grunt dan Gulp, adalah otomatis task runner, masing-masing memiliki sendiri plugin yang dapat Anda gunakan untuk mengotomatisasi Traceur kompilasi: grunt-traceur dan gulp-traceur masing-masing.
Masing-masing task runner ini dapat mengatur untuk menonton sistem file Anda dan kompilasi ulang kode instan Anda menyimpan perubahan ke file JavaScript Anda. Untuk mempelajari cara menggunakan Grunt atau Gulp, Periksa dokumentasi "Getting Started" mereka.
Kesimpulan
ES7's async fungsi menawarkan pengembang cara untuk benar-benar keluar dari callback hell yang menjanjikan tidak pernah bisa mereka sendiri. Fitur baru ini memungkinkan kita untuk menulis kode asynchronous dengan cara yang sangat mirip dengan kode synchronous, dan meskipun ES6 masih menunggu rilis penuh, kita sudah dapat menggunakan fungsi async hari ini melalui transpilation. Apa yang Anda tunggu? Pergi keluar dan membuat kode Anda yang mengagumkan!
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weekly