Sebelum mempelajari isi artikel ini, kita harus memahami terlebih dahulu konsep asynchronous. Hal pertama yang perlu ditekankan adalah ada perbedaan mendasar antara asynchronous dan parallel .
CPU
yang sama, atau pada beberapa CPU
, atau pada beberapa host fisik atau bahkan beberapa jaringan.CPU
mengesampingkan sementara tugas saat ini, memproses tugas berikutnya terlebih dahulu, dan kemudian kembali ke tugas sebelumnya untuk melanjutkan eksekusi setelah menerima pemberitahuan panggilan balik dari tugas sebelumnya benang kedua.Mungkin lebih intuitif untuk menjelaskan paralelisme, sinkronisasi, dan asinkron dalam bentuk gambar. Asumsikan ada dua tugas A dan B yang perlu diproses. Metode pemrosesan paralel, sinkron, dan asinkron akan mengadopsi metode eksekusi seperti yang ditunjukkan pada gambar berikut:
JavaScript
memberi kita banyak fungsi asinkron. Fungsi ini memungkinkan kita menjalankan tugas asinkron dengan mudah. Artinya, kita mulai menjalankan tugas (fungsi) sekarang, tetapi tugas tersebut akan diselesaikan nanti, dan waktu penyelesaiannya spesifik. tidak Tidak yakin.
Misalnya, fungsi setTimeout
adalah fungsi asinkron yang sangat umum. Selain itu, fs.readFile
dan fs.writeFile
juga merupakan fungsi asinkron.
Kita dapat mendefinisikan sendiri kasus tugas asinkron, seperti menyesuaikan fungsi penyalinan file copyFile(from,to)
:
const fs = require('fs') fungsi copyFile(dari, ke) { fs.readFile(dari, (err, data) => { jika (salah) { console.log(err.pesan) kembali } fs.writeFile(ke, data, (err) => { jika (salah) { console.log(err.pesan) kembali } console.log('Penyalinan selesai') }) }) }
Fungsi copyFile
terlebih dahulu membaca data file dari parameter from
, dan kemudian menulis data ke file yang ditunjuk oleh parameter to
.
Kita dapat memanggil copyFile
seperti ini:
copyFile('./from.txt','./to.txt')//Copy file
Jika ada kode lain setelah copyFile(...)
saat ini, program tidak akan tunggu Eksekusi copyFile
berakhir, tetapi dijalankan langsung ke bawah. Program tidak peduli kapan tugas penyalinan file berakhir.
copyFile('./dari.txt','./ke.txt') //Kode berikut tidak akan menunggu hingga eksekusi kode di atas selesai...
Sampai saat ini, semuanya tampak normal, tetapi jika kita langsung mengakses file ./to.txt
setelah copyFile(...)
fungsi Apa yang terjadi pada konten di dalamnya?
Ini tidak akan membaca konten yang disalin, lakukan saja ini:
copyFile('./from.txt','./to.txt') fs.readFile('./to.txt',(err,data)=>{ ... })
Jika file ./to.txt
belum dibuat sebelum menjalankan program, Anda akan mendapatkan error berikut:
PS E:CodeNodedemos 3-callback> node .index.js
selesai
Salinan selesai
PS E:CodeNodedemos 3-callback> simpul .index.js
Kesalahan: ENOENT: tidak ada file atau direktori seperti itu, buka 'E:CodeNodedemos 3-callbackto.txt'
Penyalinan selesai
Sekalipun ./to.txt
ada, konten yang disalin tidak dapat dibaca.
Alasan untuk fenomena ini adalah: copyFile(...)
dijalankan secara asinkron. Setelah program menjalankan fungsi copyFile(...)
, ia tidak menunggu hingga penyalinan selesai, tetapi menjalankannya langsung ke bawah, menyebabkan file tersebut. muncul kesalahan ./to.txt
tidak ada, atau kesalahan isi file kosong (jika file dibuat terlebih dahulu).
Waktu akhir eksekusi spesifik dari fungsi panggilan balik fungsi asinkron tidak dapat ditentukan. Misalnya, waktu akhir eksekusi fungsi readFile(from,to)
kemungkinan besar bergantung pada ukuran file from
.
Jadi, pertanyaannya adalah bagaimana kita dapat secara akurat menemukan akhir eksekusi copyFile
dan membaca konten file to
?
Hal ini memerlukan penggunaan fungsi callback. Kita dapat memodifikasi fungsi copyFile
sebagai berikut:
function copyFile(from, to, callback) { fs.readFile(dari, (err, data) => { jika (salah) { console.log(err.pesan) kembali } fs.writeFile(ke, data, (err) => { jika (salah) { console.log(err.pesan) kembali } console.log('Penyalinan selesai') callback()//Fungsi panggilan balik dipanggil ketika operasi penyalinan selesai}) }) }
Dengan cara ini, jika kita perlu melakukan beberapa operasi segera setelah penyalinan file selesai, kita dapat menulis operasi ini ke dalam fungsi panggilan balik:
function copyFile(from, to, callback) { fs.readFile(dari, (err, data) => { jika (salah) { console.log(err.pesan) kembali } fs.writeFile(ke, data, (err) => { jika (salah) { console.log(err.pesan) kembali } console.log('Penyalinan selesai') callback()//Fungsi panggilan balik dipanggil ketika operasi penyalinan selesai}) }) } copyFile('./from.txt', './to.txt', function () { //Masukkan fungsi callback, baca isi file "to.txt" dan output fs.readFile('./to.txt', (err, data) => { jika (salah) { console.log(err.pesan) kembali } konsol.log(data.toString()) }) })
Jika sudah menyiapkan file ./from.txt
, maka kode di atas bisa langsung dijalankan:
PS E:CodeNodedemos 3-callback> node .index.js
Salinan selesai
Bergabunglah dengan komunitas "Xianzong" dan kembangkan keabadian bersama saya. Alamat komunitas: http://t.csdn.cn/EKf1h
Metode pemrograman ini disebut gaya pemrograman asinkron "berbasis panggilan balik". Fungsi yang dijalankan secara asinkron harus menyediakan Parameter panggilan balik digunakan untuk menelepon setelah tugas berakhir.
Gaya ini umum dalam pemrograman JavaScript
. Misalnya, fungsi pembacaan file fs.readFile
dan fs.writeFile
semuanya merupakan fungsi asinkron.
fungsi panggilan balik dapat secara akurat menangani masalah berikutnya setelah pekerjaan asinkron selesai. Jika kita perlu melakukan beberapa operasi asinkron secara berurutan, kita perlu menyarangkan fungsi panggilan balik.
Skenario kasus:
Implementasi kode untuk membaca file A dan file B secara berurutan:
fs.readFile('./A.txt', (err, data) => { jika (salah) { console.log(err.pesan) kembali } console.log('Baca file A: ' + data.toString()) fs.readFile('./B.txt', (err, data) => { jika (salah) { console.log(err.pesan) kembali } console.log("Baca file B: " + data.toString()) }) })
Efek eksekusi:
PS E:CodeNodedemos 3-callback> node .index.js
Membaca file A: Sekte Abadi sangat bagus, tetapi ada yang hilang.Membaca file B: Jika Anda ingin bergabung dengan Sekte Abadi, Anda harus memiliki tautan.
http://t.csdn.cn/H1faI
Melalui panggilan balik, Anda dapat membaca file B segera setelah membaca file A.
Bagaimana jika kita ingin melanjutkan membaca file C setelah file B? Hal ini memerlukan callback yang terus disarangkan:
fs.readFile('./A.txt', (err, data) => {//Panggilan balik pertama if (err) { console.log(err.pesan) kembali } console.log('Baca file A: ' + data.toString()) fs.readFile('./B.txt', (err, data) => {//Panggilan balik kedua if (err) { console.log(err.pesan) kembali } console.log("Baca file B: " + data.toString()) fs.readFile('./C.txt',(err,data)=>{//Panggilan balik ketiga... }) }) })
Dengan kata lain, jika kita ingin melakukan beberapa operasi asinkron secara berurutan, kita memerlukan beberapa lapisan callback yang disarangkan. Hal ini efektif jika jumlah lapisannya sedikit, tetapi jika waktu penumpukannya terlalu banyak, beberapa masalah akan terjadi.
Konvensi Panggilan Balik
Faktanya, gaya fungsi panggilan balik di fs.readFile
bukanlah pengecualian, namun merupakan konvensi umum dalam JavaScript
. Kami akan menyesuaikan sejumlah besar fungsi panggilan balik di masa mendatang, dan kami harus mematuhi konvensi ini dan membentuk kebiasaan pengkodean yang baik.
Konvensinya adalah:
callback
pertama dicadangkan untuk kesalahan. Setelah terjadi kesalahan, callback(err)
akan dipanggil.callback(null, result1, result2,...)
akan dipanggil.Berdasarkan konvensi di atas, fungsi panggilan balik memiliki dua fungsi: penanganan kesalahan dan penerimaan hasil. Misalnya, fungsi panggilan balik fs.readFile('...',(err,data)=>{})
mengikuti konvensi ini.
Jika kita tidak menggali lebih dalam, metode pemrosesan asynchronous berdasarkan callback sepertinya merupakan cara yang cukup sempurna untuk menanganinya. Masalahnya adalah jika kita memiliki perilaku asinkron satu demi satu, kodenya akan terlihat seperti ini:
fs.readFile('./a.txt',(err,data)=>{ jika(salah){ console.log(err.pesan) kembali } //Baca hasil operasi fs.readFile('./b.txt',(err,data)=>{ jika(salah){ console.log(err.pesan) kembali } //Baca hasil operasi fs.readFile('./c.txt',(err,data)=>{ jika(salah){ console.log(err.pesan) kembali } //Baca hasil operasi fs.readFile('./d.txt',(err,data)=>{ jika(salah){ console.log(err.pesan) kembali } ... }) }) }) })
Isi eksekusi kode di atas adalah:
Seiring dengan meningkatnya jumlah panggilan, tingkat penyarangan kode menjadi semakin dalam, termasuk semakin banyak pernyataan kondisional, yang mengakibatkan kode membingungkan yang terus-menerus menjorok ke kanan, sehingga sulit untuk dibaca dan dipelihara.
Kami menyebut fenomena pertumbuhan terus menerus ke kanan (lekukan ke kanan) ini sebagai " callback hell " atau " pyramid of doom "!
fs.readFile('a.txt',(err,data)=>{ fs.readFile('b.txt',(err,data)=>{ fs.readFile('c.txt',(err,data)=>{ fs.readFile('d.txt',(err,data)=>{ fs.readFile('e.txt',(err,data)=>{ fs.readFile('f.txt',(err,data)=>{ fs.readFile('g.txt',(err,data)=>{ fs.readFile('h.txt',(err,data)=>{ ... /* Gerbang ke Neraka ===> */ }) }) }) }) }) }) }) })
Meskipun kode di atas terlihat cukup biasa, ini hanyalah situasi yang ideal misalnya. Biasanya terdapat sejumlah besar pernyataan kondisional, operasi pemrosesan data, dan kode lain dalam logika bisnis, yang mengganggu tatanan indah saat ini dan membuat kode tersebut. sulit untuk dipertahankan.
Untungnya, JavaScript
memberi kita banyak solusi, dan Promise
adalah solusi terbaik.