Cara menggunakan object callback javascript

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.

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, err. Callback mungkin memiliki lebih banyak argumen (yang biasanya mewakili data yang dikembalikan ke callback), tetapi yang pertama akan err. Seperti yang Anda duga, err menyimpan objek kesalahan (jika kesalahan telah dipicu — lebih lanjut tentang itu nanti).

Mari kita lihat contoh yang sangat sederhana. Kami akan menggunakan modul sistem file bawaan Node.js (fs). Di skrip ini, kita akan membaca isi file teks. Baris terakhir file adalah console.log yang menimbulkan pertanyaan: jika Anda menjalankan skrip ini, apakah Anda pikir Anda akan melihat log sebelum kita melihat isi file teks?

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 console.log terakhir sebelum konten file teks. Jika Anda memiliki file bernama a-text-file.txt di direktori yang sama di mana Anda mengeksekusi skrip node Anda, Anda akan melihat bahwa err adalah null, dan nilai text diisi dengan isi file teks.

Jika Anda tidak memiliki file bernama a-text-file.txt, err akan mengembalikan objek Error, dan nilai text akan undefined. Ini mengarah ke aspek penting dari callback: Anda harus selalu menangani kesalahanmu. Untuk menangani kesalahan, Anda perlu memeriksa nilai dalam variabel err; jika nilai ada, maka terjadi kesalahan. Dengan konvensi, err argumen yang salah biasanya tidak mengembalikan false, sehingga Anda dapat memeriksa hanya untuk kebenaran.

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:

  1. Anda memuat file secara berurutan; akan lebih efisien jika Anda dapat memuat keduanya pada saat yang sama dan mengembalikan nilai ketika keduanya telah dimuat sepenuhnya.

  2. Secara sintaks itu benar tetapi sulit dibaca. Perhatikan jumlah fungsi yang disarangkan dan tab yang bertambah. Anda dapat melakukan beberapa trik untuk membuatnya terlihat sedikit lebih baik, tetapi Anda mungkin mengorbankan keterbacaan dengan cara lain.

  3. Itu bukan tujuan yang sangat umum. Ini berfungsi dengan baik untuk dua file, tetapi bagaimana jika Anda memiliki sembilan file kadang-kadang dan lain kali 22 atau hanya satu? Cara menulisnya saat ini sangat kaku.

Jangan khawatir, kami dapat menyelesaikan semua masalah ini (dan banyak lagi) dengan async.js.

Callback dengan Async.js

Pertama, 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). async.series mengambil serangkaian fungsi dan menjalankannya satu demi satu.

Setiap fungsi hanya boleh memiliki satu argumen, callback (atau cb dalam kode kita). cb harus dieksekusi dengan jenis argumen yang sama seperti callback lainnya, sehingga kita dapat memasukkannya ke dalam argumen fs.readFile kami.

Akhirnya, hasilnya dikirim ke panggilan balik terakhir, argumen kedua ke async.series. Hasilnya disimpan dalam larik dengan nilai yang terkait dengan urutan fungsi dalam argumen pertama async.series.

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 Sekarang

Fungsi terkait adalah async.parallel; ia memiliki argumen yang sama dengan async.series sehingga Anda dapat mengubah antara keduanya tanpa mengubah sisa sintaks Anda. Ini adalah titik yang bagus untuk mencakup paralel versus bersamaan.

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 async.parallel, Anda tidak membuatnya membuka utas lain untuk mengurai JavaScript atau melakukan dua hal sekaligus — Anda benar-benar mengontrol ketika melewati fungsi di argumen pertama async.parallel. Jadi Anda tidak memperoleh apa pun dengan hanya menempatkan kode sinkron di async.parallel.

Ini paling baik dijelaskan secara visual:

Cara menggunakan object callback javascript
Cara menggunakan object callback javascript
Cara menggunakan object callback javascript

Berikut adalah contoh kami sebelumnya yang ditulis menjadi paralel — satu-satunya perbedaan adalah bahwa kami menggunakan async.parallel daripada async.series.

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 lagi

Contoh 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 async.times. Dalam contoh ini, callback untuk fs.writeFile hanya membuat err argumen, tetapi fungsi async.times juga dapat mendukung nilai pengembalian. Seperti async.series, itu dilewatkan ke callback dilakukan dalam argumen kedua sebagai array.

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 async.times menjadi async.timesSeries.

Untuk contoh pengulangan berikutnya, kita akan melihat fungsi async.until. async.until mengeksekusi fungsi asynchronous (secara seri) hingga kondisi tertentu terpenuhi. Fungsi ini mengambil tiga fungsi sebagai argumen.

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 console.log ke dalam fungsi pengujian atau fungsi asinkron, Anda akan mendapatkan hasil yang sangat berbeda karena ini membutuhkan waktu untuk menulis ke konsol Anda. Itu hanya menunjukkan bahwa dalam perangkat lunak, semuanya memiliki biaya kinerja!

Untuk setiap loop adalah struktur yang berguna — memungkinkan Anda melakukan sesuatu untuk setiap item dari sebuah array. Di async.js, ini akan menjadi fungsi async.each. Fungsi ini membutuhkan tiga argumen: koleksi atau larik, fungsi asinkron untuk melakukan untuk setiap item, dan callback selesai.

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 err dalam callback yang dilakukan. async.each dijalankan secara paralel, tetapi jika Anda ingin menjalankannya secara seri, Anda dapat mengikuti pola yang disebutkan sebelumnya dan menggunakan async.eachSeries alih-alih async.each.

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 async.each adalah fungsi async.map; perbedaannya adalah Anda dapat mengembalikan nilai ke callback yang Anda lakukan. Dengan fungsi async.map, Anda mengirimkan array atau koleksi sebagai argumen pertama, dan kemudian fungsi asynchronous akan dijalankan pada setiap item dalam larik atau koleksi. Argumen terakhir adalah callback yang dilakukan.

Contoh di bawah ini mengambil berbagai ras anjing dan menggunakan setiap item untuk membuat nama file. Nama file ini kemudian diteruskan ke fs.readFile, di mana ia dibaca dan nilainya dilewatkan kembali oleh fungsi callback. Anda berakhir dengan array isi file dalam argumen callback selesai.

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);
    }
  }
);

async.filter juga sangat mirip dalam sintaks ke async.each dan async.map, tetapi dengan filter Anda mengirim nilai boolean ke item callback daripada nilai file. Dalam callback yang dilakukan, Anda mendapatkan array baru, dengan hanya elemen yang Anda berikan nilai yang true atau truthy ​​untuk di item callback.

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 if err dan callback(err) sangat berguna jika Anda perlu memanipulasi hasil fungsi asynchronous, tetapi Anda tetap ingin membiarkan async.js menangani kesalahan.

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 Tebing

Sejauh ini, kami telah menjelajahi sejumlah blok bangunan yang berguna yang memiliki konsekuensi yang tidak jelas dalam pemrograman sinkron. Mari kita langsung masuk ke async.waterfall, yang tidak memiliki banyak persamaan dalam dunia sinkron.

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 async.waterfall, argumen pertama adalah array fungsi, dan argumen kedua adalah callback selesai Anda.

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.

Cara menggunakan object callback javascript
Cara menggunakan object callback javascript
Cara menggunakan object callback javascript

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 async.map untuk menjalankan fs.stat pada setiap file, dan fungsi ketiga mengambil daftar direktori dari hasil fungsi pertama dan mendapatkan konten untuk setiap file (fs.readFile).

async.waterfall menjalankan setiap fungsi secara berurutan, sehingga selalu menjalankan semua fungsi fs.stat sebelum menjalankan fs.readFile apa pun. Pada contoh pertama ini, fungsi kedua dan ketiga tidak bergantung satu sama lain sehingga fungsi dapat dibungkus dalam async.parallel untuk mengurangi waktu eksekusi total, tetapi kami akan mengubah struktur ini lagi untuk contoh berikutnya.

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 Array.filter, tetapi itu adalah fungsi sinkron, dan async.waterfall mengharapkan fungsi-fungsi asynchronous-style. Async.js memiliki fungsi pembantu yang dapat membungkus fungsi-fungsi sinkron ke dalam fungsi asynchronous, yang agak beradab alias async.asyncify.

Kita perlu melakukan tiga hal, yang semuanya akan kita bungkus dengan async.asyncify. Pertama, kami akan mengambil nama file dan susunan stat dari fungsi arrayFsStat, dan kami akan menggabungkannya menggunakan map. Lalu kami akan memfilter item apa pun yang memiliki ukuran statistik kurang dari 300. Terakhir, kami akan mengambil gabungan nama file dan stat objek dan menggunakan map lagi untuk hanya mengeluarkan nama file.

Setelah kami mendapatkan nama file dengan ukuran kurang dari 300, kami akan menggunakan async.map dan fs.readFile untuk mendapatkan isinya. Ada banyak cara untuk memecahkan telur ini, tetapi dalam kasus kami dipecahkan untuk menunjukkan fleksibilitas maksimum dan penggunaan kembali kode. Penggunaan async.waterfall ini menggambarkan bagaimana Anda dapat mencampur dan mencocokkan kode sinkron dan asinkron.

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 async.seq. Sementara async.waterfall hanya menjalankan fungsi air terjun, async.seq mengembalikan fungsi yang melakukan waterfall fungsi lain. Selain membuat fungsi, Anda dapat meneruskan nilai yang akan masuk ke fungsi asinkron pertama.

Konversi ke async.seq hanya membutuhkan sedikit modifikasi. Pertama, kami akan mengubah directoryListing untuk menerima argumen — ini akan menjadi jalurnya. Kedua, kami akan menambahkan variabel untuk menahan fungsi baru kami (directoryAbove300). Ketiga, kami akan mengambil argumen array dari async.waterfall dan menerjemahkannya ke dalam argumen untuk async.seq. Callback yang kami lakukan untuk waterfall sekarang digunakan sebagai callback yang dilakukan ketika kami menjalankan directoryAbove300.

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 Async

Anda 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 err-first, dan ribuan modul lain menggunakan pola ini. Bahkan, itulah sebabnya mengapa tutorial ini menggunakan fs dalam contoh — sesuatu yang mendasar seperti akses sistem file di Node.js menggunakan callback, jadi memperumit kode callback tanpa janji adalah bagian penting dari pemrograman Node.js.

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 Asynchrony

JavaScript 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.