Saat pertama kali memulai pemrograman, kami belajar bahwa blok kode dijalankan dari atas ke bawah. Ini adalah pemrograman sinkron: setiap operasi selesai sebelum yang berikutnya dimulai. Ini bagus ketika Anda melakukan banyak hal yang praktis tidak ada waktu bagi komputer untuk menyelesaikan, seperti menambahkan angka, memanipulasi string, atau menetapkan variabel. Show Apa yang terjadi ketika Anda ingin melakukan sesuatu yang membutuhkan waktu yang relatif lama, seperti mengakses file pada disk, mengirim permintaan jaringan, atau menunggu timer untuk berlalu? Dalam pemrograman sinkron, skrip Anda tidak dapat melakukan apa pun saat menunggu. Ini mungkin baik-baik saja untuk sesuatu yang sederhana atau dalam situasi di mana Anda akan memiliki beberapa contoh skrip Anda berjalan, tetapi untuk banyak aplikasi server, itu adalah mimpi buruk. Masukkan pemrograman asynchronous. Dalam skrip asynchronous, kode Anda terus dijalankan sambil menunggu sesuatu terjadi, tetapi dapat melompat kembali ketika sesuatu itu terjadi. Ambil, misalnya, permintaan jaringan. Jika Anda membuat permintaan jaringan ke server lambat yang membutuhkan waktu tiga detik penuh untuk merespon, skrip Anda dapat secara aktif melakukan hal-hal lain saat server lambat ini merespons. Dalam hal ini, tiga detik mungkin tidak terlihat seperti manusia, tetapi server dapat menanggapi ribuan permintaan lainnya saat menunggu. Jadi, bagaimana Anda menangani
asynchrony di Node.js? Cara paling mendasar adalah melalui callback. Callback hanya fungsi yang dipanggil ketika operasi asynchronous selesai. Berdasarkan konvensi, fungsi callback Node.js memiliki setidaknya satu argumen, Mari kita lihat contoh yang sangat sederhana. Kami akan menggunakan modul sistem file bawaan Node.js ( var fs = require('fs'); fs.readFile( 'a-text-file.txt', //the filename of a text file that says "Hello!" 'utf8', //the encoding of the file, in this case, utf-8 function(err,text) { //the callback console.log('Error:',err); //Errors, if any console.log('Text:',text); //the contents of the file } ); //Will this be before or after the Error / Text? console.log('Does this get logged before or after the contents of the text file?'); Karena ini tidak sinkron, kita sebenarnya akan melihat Jika Anda tidak memiliki file bernama a-text-file.txt, var fs = require('fs'); fs.readFile( 'a-text-file.txt', //the filename of a text file that says "Hello!" 'utf8', //the encoding of the file, in this case, utf-8 function(err,text) { //the callback if (err) { console.error(err); //display an error to the console } else { console.log('Text:',text); //no error, so display the contents of the file } } ); Sekarang, katakanlah Anda ingin menampilkan konten dari dua file dalam urutan tertentu. Anda akan mendapatkan sesuatu seperti ini: var fs = require('fs'); fs.readFile( 'a-text-file.txt', //the filename of a text file that says "Hello!" 'utf8', //the encoding of the file, in this case, utf-8 function(err,text) { //the callback if (err) { console.error(err); //display an error to the console } else { console.log('First text file:',text); //no error, so display the contents of the file fs.readFile( 'another-text-file.txt', //the filename of a text file that says "Hello!" 'utf8', //the encoding of the file, in this case, utf-8 function(err,text) { //the callback if (err) { console.error(err); //display an error to the console } else { console.log('Second text file:',text); //no error, so display the contents of the file } } ); } } ); Kode terlihat sangat buruk dan memiliki sejumlah masalah:
Jangan khawatir, kami dapat menyelesaikan semua masalah ini (dan banyak lagi) dengan async.js. Callback dengan Async.jsPertama, mari kita mulai dengan menginstal modul async.js. npm install async —-save Async.js dapat digunakan untuk merekatkan array fungsi dalam serial atau paralel. Mari kita tulis ulang contoh kita: var async = require('async'), //async.js module fs = require('fs'); async.series( //execute the functions in the first argument one after another [ //The first argument is an array of functions function(cb) { //`cb` is shorthand for "callback" fs.readFile( 'a-text-file.txt', 'utf8', cb ); }, function(cb) { fs.readFile( 'another-text-file.txt', 'utf8', cb ); } ], function(err,values) { //The "done" callback that is ran after the functions in the array have completed if (err) { //If any errors occurred when functions in the array executed, they will be sent as the err. console.error(err); } else { //If err is falsy then everything is good console.log('First text file:',values[0]); console.log('Second text file:',values[1]); } } ); Ini berfungsi hampir sama seperti contoh sebelumnya, secara berurutan memuat setiap file, dan hanya berbeda dalam membaca setiap file dan tidak menampilkan hasilnya sampai selesai. Kode lebih ringkas dan lebih bersih daripada contoh sebelumnya (dan kami akan membuatnya lebih baik nantinya). Setiap fungsi hanya
boleh memiliki satu argumen, callback (atau Akhirnya, hasilnya dikirim ke panggilan balik terakhir, argumen kedua ke Dengan async.js, penanganan kesalahan disederhanakan karena jika menemui kesalahan, ia mengembalikan kesalahan ke argumen callback terakhir dan tidak akan menjalankan fungsi asynchronous lebih lanjut. Semua Bersama SekarangFungsi terkait adalah JavaScript pada dasarnya adalah bahasa single-threaded, yang berarti hanya dapat melakukan satu hal pada satu waktu. Hal ini mampu melakukan beberapa tugas dalam utas yang terpisah (kebanyakan fungsi I/O, misalnya), dan ini adalah di mana pemrograman asynchronous ikut bermain dengan JS. Jangan bingung sejajar dengan konkurensi. Saat Anda menjalankan dua hal dengan Ini paling baik dijelaskan secara visual: Berikut
adalah contoh kami sebelumnya yang ditulis menjadi paralel — satu-satunya perbedaan adalah bahwa kami menggunakan var async = require('async'), //async.js module fs = require('fs'); async.parallel( //execute the functions in the first argument, but don't wait for the first function to finish to start the second [ //The first argument is an array of functions function(cb) { //`cb` is shorthand for "callback" fs.readFile( 'a-text-file.txt', 'utf8', cb ); }, function(cb) { fs.readFile( 'another-text-file.txt', 'utf8', cb ); } ], function(err,values) { //The "done" callback that is ran after the functions in the array have completed if (err) { //If any errors occurred when functions in the array executed, they will be sent as the err. console.error(err); } else { //If err is falsy then everything is good console.log('First text file:',values[0]); console.log('Second text file:',values[1]); } } ); Lagi dan lagiContoh kami sebelumnya telah menjalankan sejumlah operasi tetap, tetapi apa yang terjadi jika Anda memerlukan sejumlah variabel operasi asynchronous? Hal ini menjadi cepat berantakan jika Anda hanya mengandalkan callback dan konstruksi bahasa reguler, mengandalkan counter atau pemeriksaan kondisi yang kikuk yang mengaburkan arti sebenarnya dari kode Anda. Mari kita lihat persamaan kasar dari loop for async.js. Dalam contoh ini, kami akan menulis sepuluh file ke direktori saat ini dengan nama file berurutan dan beberapa konten singkat. Anda dapat memvariasikan angka dengan mengubah nilai argumen pertama var async = require('async'), fs = require('fs'); async.times( 10, // number of times to run the function function(runCount,callback) { fs.writeFile( 'file-'+runCount+'.txt', //the new file name 'This is file number '+runCount, //the contents of the new file callback ); }, function(err) { if (err) { console.error(err); } else { console.log('Wrote files.'); } } ); Ini saat yang tepat untuk mengatakan bahwa sebagian besar fungsi async.js, secara default, berjalan secara paralel daripada seri. Jadi, dalam contoh di atas, ia akan mulai membuat file dan melaporkan saat semuanya dibuat dan ditulis sepenuhnya. Fungsi-fungsi yang berjalan secara paralel secara default memiliki fungsi seri yang wajar yang ditunjukkan oleh fungsi yang diakhiri dengan, Anda dapat menebaknya, 'Seri'. Jadi jika Anda
ingin menjalankan contoh ini secara seri daripada paralel, Anda akan mengubah Untuk contoh pengulangan berikutnya, kita akan melihat fungsi async.until. Fungsi pertama adalah tes di mana Anda mengembalikan true (jika Anda ingin menghentikan loop) atau false (jika Anda ingin melanjutkan loop). Argumen kedua adalah fungsi asynchronous, dan yang terakhir adalah callback yang dilakukan. Lihatlah contoh ini: var async = require('async'), fs = require('fs'), startTime = new Date().getTime(), //the unix timestamp in milliseconds runCount = 0; async.until( function () { //return true if 4 milliseconds have elapsed, otherwise false (and continue running the script) return new Date().getTime() > (startTime + 5); }, function(callback) { runCount += 1; fs.writeFile( 'timed-file-'+runCount+'.txt', //the new file name 'This is file number '+runCount, //the contents of the new file callback ); }, function(err) { if (err) { console.error(err); } else { console.log('Wrote files.'); } } ); Skrip ini akan membuat file teks baru selama lima milidetik. Di awal skrip, kita mendapatkan waktu mulai di milisetond unix epoch, dan kemudian dalam fungsi pengujian kita mendapatkan waktu saat ini dan menguji untuk melihat apakah itu lima milidetik lebih besar dari waktu mulai ditambah lima. Jika Anda menjalankan skrip ini beberapa kali, Anda mungkin mendapatkan hasil yang berbeda. Di
mesin saya, saya membuat antara 6 dan 20 file dalam lima milidetik. Menariknya, jika Anda mencoba menambahkan Untuk setiap loop adalah struktur yang berguna — memungkinkan Anda melakukan sesuatu untuk setiap item dari sebuah array. Di async.js,
ini akan menjadi fungsi Dalam contoh di bawah ini, kami mengambil serangkaian string (dalam hal ini jenis ras anjing sighthound) dan membuat file untuk setiap string. Ketika semua file telah dibuat, callback selesai dijalankan. Seperti yang Anda duga, kesalahan ditangani melalui objek var async = require('async'), fs = require('fs'); async.each( //an array of sighthound dog breeds ['greyhound','saluki','borzoi','galga','podenco','whippet','lurcher','italian-greyhound'], function(dogBreed, callback) { fs.writeFile( dogBreed+'.txt', //the new file name 'file for dogs of the breed '+dogBreed, //the contents of the new file callback ); }, function(err) { if (err) { console.error(err); } else { console.log('Done writing files about dogs.'); } } ); Sepupu Contoh di bawah ini mengambil berbagai ras anjing dan menggunakan setiap item untuk membuat nama file. Nama file ini kemudian diteruskan ke var async = require('async'), fs = require('fs'); async.map( ['greyhound','saluki','borzoi','galga','podenco','whippet','lurcher','italian-greyhound'], function(dogBreed, callback) { fs.readFile( dogBreed+'.txt', //the new file name 'utf8', callback ); }, function(err, dogBreedFileContents) { if (err) { console.error(err); } else { console.log('dog breeds'); console.log(dogBreedFileContents); } } );
var async = require('async'), fs = require('fs'); async.filter( ['greyhound','saluki','borzoi','galga','podenco','whippet','lurcher','italian-greyhound'], function(dogBreed, callback) { fs.readFile( dogBreed+'.txt', //the new file name 'utf8', function(err,fileContents) { if (err) { callback(err); } else { callback( err, //this will be falsy since we checked it above fileContents.match(/greyhound/gi) //use RegExp to check for the string 'greyhound' in the contents of the file ); } } ); }, function(err, dogBreedFileContents) { if (err) { console.error(err); } else { console.log('greyhound breeds:'); console.log(dogBreedFileContents); } } ); Dalam contoh ini, kami melakukan beberapa hal lagi dari pada contoh sebelumnya. Perhatikan bagaimana kami menambahkan panggilan fungsi tambahan dan menangani kesalahan kami sendiri. Pola Selain itu, Anda akan melihat bahwa kami menggunakan variabel err sebagai argumen pertama untuk fungsi panggilan balik. Pada awalnya, ini tidak terlihat benar. Tetapi karena kami sudah memeriksa kebenaran err, kami tahu itu salah dan aman untuk diteruskan ke panggilan balik. Di Tepi TebingSejauh ini, kami telah menjelajahi sejumlah blok bangunan yang berguna yang memiliki konsekuensi yang tidak jelas dalam pemrograman sinkron. Mari kita langsung masuk ke Konsep dengan waterfall adalah bahwa hasil dari satu aliran fungsi asynchronous ke argumen dari fungsi asynchronous lain dalam seri. Ini adalah konsep yang sangat kuat, terutama ketika mencoba menyatukan beberapa fungsi
asinkron yang bergantung satu sama lain. Dengan Di dalam array fungsi Anda, fungsi pertama akan selalu dimulai dengan argumen tunggal, callback. Setiap fungsi berikutnya harus sesuai dengan argumen non-err dari fungsi sebelumnya tanpa fungsi err dan dengan penambahan callback baru. Dalam contoh kami berikutnya, kami akan mulai menggabungkan beberapa konsep menggunakan air terjun sebagai perekat. Dalam array yang merupakan argumen pertama, kami
memiliki tiga fungsi: yang pertama memuat daftar direktori dari direktori saat ini, yang kedua mengambil daftar direktori dan menggunakan
Catatan: Menjalankan contoh ini di direktori file teks kecil, jika tidak Anda akan mendapatkan banyak sampah untuk waktu yang lama di jendela terminal. var async = require('async'), fs = require('fs'); async.waterfall([ function(callback) { fs.readdir('.',callback); //read the current directory, pass it along to the next function. }, function(fileNames,callback) { //`fileNames` is the directory listing from the previous function async.map( fileNames, //The directory listing is just an array of filenames, fs.stat, //so we can use async.map to run fs.stat for each filename function(err,stats) { if (err) { callback(err); } else { callback(err,fileNames,stats); //pass along the error, the directory listing and the stat collection to the next item in the waterfall } } ); }, function(fileNames,stats,callback) { //the directory listing, `fileNames` is joined by the collection of fs.stat objects in `stats` async.map( fileNames, function(aFileName,readCallback) { //This time we're taking the filenames with map and passing them along to fs.readFile to get the contents fs.readFile(aFileName,'utf8',readCallback); }, function(err,contents) { if (err) { callback(err); } else { //Now our callback will have three arguments, the original directory listing (`fileNames`), the fs.stats collection and an array of with the contents of each file callback(err,fileNames,stats,contents); } } ); } ], function(err, fileNames,stats,contents) { if (err) { console.error(err); } else { console.log(fileNames); console.log(stats); console.log(contents); } } ); Katakanlah kita ingin mendapatkan hasil dari hanya file yang memiliki ukuran di atas 500 byte. Kami dapat menggunakan kode di atas, tetapi Anda akan mendapatkan ukuran dan konten dari setiap file, apakah Anda membutuhkannya atau tidak. Bagaimana Anda bisa mendapatkan stat file dan hanya isi file yang mencapai persyaratan ukuran? Pertama, kita dapat menarik semua fungsi anonim ke dalam fungsi bernama. Ini adalah preferensi pribadi, tetapi itu membuat kode sedikit lebih bersih dan lebih mudah dipahami (dapat digunakan kembali untuk boot). Seperti yang Anda bayangkan, Anda harus
mendapatkan ukuran, mengevaluasi ukuran tersebut, dan hanya mendapatkan konten file di atas persyaratan ukuran. Ini dapat dengan mudah dicapai dengan sesuatu seperti Kita perlu melakukan tiga hal, yang semuanya akan kita bungkus dengan
Setelah kami mendapatkan nama file dengan ukuran kurang dari 300, kami akan menggunakan var async = require('async'), fs = require('fs'); //Our anonymous refactored into named functions function directoryListing(callback) { fs.readdir('.',callback); } function arrayFsStat(fileNames,callback) { async.map( fileNames, fs.stat, function(err,stats) { if (err) { callback(err); } else { callback(err,fileNames,stats); } } ); } function arrayFsReadFile(fileNames,callback) { async.map( fileNames, function(aFileName,readCallback) { fs.readFile(aFileName,'utf8',readCallback); }, function(err,contents) { if (err) { callback(err); } else { callback(err,contents); } } ); } //These functions are synchronous function mergeFilenameAndStat(fileNames,stats) { return stats.map(function(aStatObj,index) { aStatObj.fileName = fileNames[index]; return aStatObj; }); } function above300(combinedFilenamesAndStats) { return combinedFilenamesAndStats .filter(function(aStatObj) { return aStatObj.size >= 300; }); } function justFilenames(combinedFilenamesAndStats) { return combinedFilenamesAndStats .map(function(aCombinedFileNameAndStatObj) { return aCombinedFileNameAndStatObj.fileName; }); } async.waterfall([ directoryListing, arrayFsStat, async.asyncify(mergeFilenameAndStat), //asyncify wraps synchronous functions in a err-first callback async.asyncify(above300), async.asyncify(justFilenames), arrayFsReadFile ], function(err,contents) { if (err) { console.error(err); } else { console.log(contents); } } ); Mengambil satu langkah lebih jauh, mari kita perbaiki fungsi kita lebih jauh. Katakanlah kita ingin menulis fungsi yang berfungsi persis seperti di atas, tetapi dengan fleksibilitas untuk melihat di jalur
mana pun. Sepupu dekat ke async.waterfall adalah Konversi ke var async = require('async'), fs = require('fs'), directoryAbove300; function directoryListing(initialPath,callback) { //we can pass a variable into the first function used in async.seq - the resulting function can accept arguments and pass them this first function fs.readdir(initialPath,callback); } function arrayFsStat(fileNames,callback) { async.map( fileNames, fs.stat, function(err,stats) { if (err) { callback(err); } else { callback(err,fileNames,stats); } } ); } function arrayFsReadFile(fileNames,callback) { async.map( fileNames, function(aFileName,readCallback) { fs.readFile(aFileName,'utf8',readCallback); }, function(err,contents) { if (err) { callback(err); } else { callback(err,contents); } } ); } function mergeFilenameAndStat(fileNames,stats) { return stats.map(function(aStatObj,index) { aStatObj.fileName = fileNames[index]; return aStatObj; }); } function above300(combinedFilenamesAndStats) { return combinedFilenamesAndStats .filter(function(aStatObj) { return aStatObj.size >= 300; }); } function justFilenames(combinedFilenamesAndStats) { return combinedFilenamesAndStats .map(function(aCombinedFileNameAndStatObj) { return aCombinedFileNameAndStatObj.fileName; }) } //async.seq will produce a new function that you can use over and over directoryAbove300 = async.seq( directoryListing, arrayFsStat, async.asyncify(mergeFilenameAndStat), async.asyncify(above300), async.asyncify(justFilenames), arrayFsReadFile ); directoryAbove300( '.', function(err, fileNames,stats,contents) { if (err) { console.error(err); } else { console.log(fileNames); } } ); Catatan tentang Promises dan Fungsi AsyncAnda mungkin bertanya-tanya mengapa saya belum menyebutkan promises. Saya tidak punya apa-apa melawan mereka — mereka cukup berguna dan mungkin solusi yang lebih elegan daripada callback - tetapi itu adalah cara yang berbeda untuk melihat pengkodean tidak sinkron. Built-in modul Node.js menggunakan callback Ada kemungkinan untuk menggunakan sesuatu seperti Bluebird untuk membungkus panggilan balik err-first ke dalam fungsi Promise-based, tetapi itu hanya membuat Anda sejauh ini — Async.js menyediakan sejumlah metafora yang membuat kode tidak tersinkronisasi dapat dibaca dan dikelola. Rangkul AsynchronyJavaScript telah menjadi salah satu bahasa de fakto bekerja di web. Bukan tanpa kurva belajar, dan ada banyak kerangka kerja dan perpustakaan untuk membuat Anda sibuk juga. Jika Anda mencari sumber daya tambahan untuk dipelajari atau digunakan dalam pekerjaan Anda, periksa apa yang
kami miliki di Envato marketplace. Namun belajar asinkron adalah sesuatu yang sangat berbeda, dan semoga, tutorial ini telah menunjukkan kepada Anda betapa bermanfaatnya hal itu. Asynchrony adalah kunci untuk menulis JavaScript sisi server, namun jika itu tidak dibuat dengan benar, kode Anda dapat menjadi binatang pemanggil yang tidak terkendali. Dengan menggunakan pustaka seperti async.js yang menyediakan sejumlah metafora, Anda mungkin menemukan bahwa menulis kode asinkron adalah suatu kegembiraan. |