Artikel ini adalah beberapa pemahaman pribadi tentang nodejs dalam pengembangan dan pembelajaran aktual. Artikel ini sekarang disusun untuk referensi di masa mendatang. Saya akan merasa terhormat jika dapat menginspirasi Anda.
I/O : Input/Output, masukan dan keluaran suatu sistem.
Suatu sistem dapat dipahami sebagai seorang individu, seperti seseorang. Ketika Anda berbicara, itu adalah keluarannya, dan ketika Anda mendengarkan, itu adalah masukannya.
Perbedaan antara I/O pemblokiran dan I/O non-pemblokiran terletak pada apakah sistem dapat menerima masukan lain selama periode dari masukan ke keluaran .
Berikut adalah dua contoh untuk mengilustrasikan apa yang dimaksud dengan I/O pemblokiran dan I/O non-pemblokiran:
1. Memasak
Pertama-tama, kita perlu menentukan ruang lingkup suatu sistem. Dalam contoh ini, bibi kantin dan pelayan di restoran dianggap sebagai suatu sistem.
Kemudian apakah Anda dapat menerima pesanan orang lain antara memesan dan menyajikan makanan, Anda dapat menentukan apakah itu memblokir I/O atau non-blocking I/O.
Sedangkan untuk bibi kantin, ia tidak dapat memesan untuk siswa lain saat memesan. Baru setelah siswa tersebut selesai memesan dan menyajikan hidangan, barulah ia dapat menerima pesanan siswa berikutnya, sehingga bibi kantin memblokir I/O.
Bagi seorang pelayan restoran, ia dapat melayani tamu berikutnya setelah memesan dan sebelum tamu menyajikan hidangan, sehingga pelayan tersebut memiliki I/O yang tidak menghalangi.
2. Mengerjakan pekerjaan rumah
Saat mencuci pakaian, Anda tidak perlu menunggu mesin cuci, Anda bisa menyapu lantai dan merapikan meja saat ini. Setelah merapikan meja, pakaian dicuci dan Anda bisa menjemur pakaian hingga kering totalnya 25 menit.
Binatu sebenarnya adalah I/O non-blocking. Anda dapat melakukan hal lain antara memasukkan pakaian ke dalam mesin cuci dan menyelesaikan pencucian.
Alasan mengapa I/O non-pemblokiran dapat meningkatkan kinerja adalah karena dapat menghemat waktu tunggu yang tidak perlu.
Kunci untuk memahami I/O non-blocking adalah
Bagaimana I/O non-pemblokiran dari nodejs tercermin? Seperti disebutkan sebelumnya, poin penting dalam memahami I/O non-blocking adalah menentukan batas sistem terlebih dahulu. Batas sistem dari node adalah thread utama .
Jika diagram arsitektur di bawah ini dibagi menurut pemeliharaan thread, garis putus-putus di sebelah kiri adalah thread nodejs, dan garis putus-putus di sebelah kanan adalah thread C++.
Sekarang thread nodejs perlu menanyakan database. Ini adalah operasi I/O yang khas. Ini tidak akan menunggu hasil I/O dan terus memproses operasi lainnya benang untuk perhitungan.
Tunggu hingga hasilnya keluar dan kembalikan ke thread nodejs. Sebelum mendapatkan hasilnya, thread nodejs juga dapat melakukan operasi I/O lainnya, sehingga non-blocking.
Thread nodejs setara dengan bagian kiri sebagai pelayan, dan thread c++ sebagai koki.
Oleh karena itu, I/O non-pemblokiran node diselesaikan dengan memanggil thread pekerja C++.
Jadi bagaimana cara memberi tahu thread nodejs ketika thread c++ mendapatkan hasilnya? Jawabannya didorong oleh peristiwa .
Pemblokiran: proses tertidur selama I/O dan menunggu hingga I/O selesai sebelum melanjutkan ke langkah berikutnya;
non-pemblokiran : fungsi segera kembali selama I/O dan proses tidak menunggu I/O. HAI untuk menyelesaikan.
Jadi bagaimana mengetahui hasil yang dikembalikan, Anda perlu menggunakan event driver .
Yang disebut event-driven bisa diartikan sama dengan event klik front-end. Saya pertama kali menulis event klik, tapi saya tidak tahu kapan akan terpicu menjalankan fungsi berbasis peristiwa.
Mode ini juga merupakan mode pengamat, yaitu saya mendengarkan acara terlebih dahulu, lalu menjalankannya saat dipicu.
Jadi bagaimana cara mengimplementasikan event drive? Jawabannya adalah pemrograman asynchronous .
Seperti disebutkan di atas, nodejs memiliki sejumlah besar I/O non-pemblokiran, sehingga hasil I/O non-pemblokiran perlu diperoleh melalui fungsi panggilan balik. Metode penggunaan fungsi panggilan balik ini adalah pemrograman asinkron . Misalnya, kode berikut memperoleh hasil melalui fungsi panggilan balik:
glob(__dirname+'/**/*', (err, res) => { hasil = res console.log('dapatkan hasil') })
Parameter pertama dari fungsi panggilan balik nodejs adalah kesalahan, dan parameter selanjutnya adalah hasilnya . Mengapa melakukan ini?
mencoba { wawancara(fungsi () { console.log('senyum') }) } tangkapan(salah) { console.log('menangis', err) } wawancara fungsi (panggilan balik) { setWaktu habis(() => { if(Matematika.acak() < 0,1) { panggilan balik('sukses') } kalau tidak { melempar Kesalahan baru ('gagal') } }, 500) }
Setelah dieksekusi, tidak tertangkap dan kesalahan terjadi secara global, menyebabkan seluruh program nodejs mogok.
Itu tidak ditangkap oleh try catch karena setTimeout membuka kembali loop peristiwa. Setiap kali loop peristiwa dibuka, konteks tumpukan panggilan dibuat ulang. Try catch milik tumpukan panggilan dari loop peristiwa sebelumnya. Ketika fungsi panggilan balik setTimeout dijalankan, konteks tumpukan panggilan Semuanya berbeda. Tidak ada try catch di tumpukan panggilan baru ini, sehingga kesalahan terjadi secara global dan tidak dapat ditangkap. Untuk detailnya, silakan merujuk ke artikel ini. Masalah saat melakukan try catch dalam antrian asinkron.
Jadi apa yang harus dilakukan? Berikan kesalahan sebagai parameter:
function interview(callback) { setWaktu habis(() => { if(Matematika.acak() < 0,5) { panggilan balik('sukses') } kalau tidak { panggilan balik (Kesalahan baru ('gagal')) } }, 500) } wawancara(fungsi (res) { if (res instanceof Kesalahan) { console.log('menangis') kembali } console.log('senyum') })
Tapi ini lebih merepotkan, dan harus membuat penilaian di callback, jadi ada aturan yang matang. Parameter pertama adalah err.
wawancara fungsi (panggilan balik) { setWaktu habis(() => { if(Matematika.acak() < 0,5) { panggilan balik (null, 'sukses') } kalau tidak { panggilan balik (Kesalahan baru ('gagal')) } }, 500) } wawancara(fungsi (res) { jika (res) { kembali } console.log('senyum') })Metode penulisan panggilan balik dari nodejs
tidak hanya akan menimbulkan area panggilan balik, tetapi juga menimbulkan masalah kontrol proses asinkron .
Kontrol proses asinkron terutama mengacu pada cara menangani logika konkurensi ketika konkurensi terjadi. Masih menggunakan contoh di atas, jika rekan Anda mewawancarai dua perusahaan, dia tidak akan diwawancarai oleh perusahaan ketiga sampai dia berhasil mewawancarai dua perusahaan tersebut. Anda perlu menambahkan variabel count secara global:
var count = 0 wawancara((salah) => { jika (salah) { kembali } hitung++ jika (hitung >= 2) { // Memproses logika} }) wawancara((salah) => { jika (salah) { kembali } hitung++ jika (hitung >= 2) { // Memproses logika} })
Tulisan seperti di atas sangat merepotkan dan jelek. Oleh karena itu, metode penulisan janji dan async/menunggu muncul kemudian.
bahwa perulangan peristiwa saat ini tidak dapat memperoleh hasil, tetapi perulangan peristiwa di masa depan akan memberi Anda hasilnya. Ini sangat mirip dengan apa yang dikatakan bajingan.
Janji bukan hanya bajingan, tetapi juga mesin negara:
const pro = new Promise((resolve, reject) => { setWaktu habis(() => { menyelesaikan('2') }, 200) }) console.log(pro) // Print: Promise { <pending> }
Mengeksekusi kemudian atau menangkap akan mengembalikan janji baru . Keadaan akhir dari janji ditentukan oleh hasil eksekusi fungsi panggilan balik kemudian dan menangkap:
fungsi wawancara() { kembalikan Janji baru((putuskan, tolak) => { setWaktu habis(() => { if (Matematika.acak() > 0,5) { menyelesaikan('sukses') } kalau tidak { tolak(Kesalahan baru('gagal')) } }) }) } var janji = wawancara() var janji1 = janji.lalu(() => { kembalikan Janji baru((putuskan, tolak) => { setWaktu habis(() => { tekad('menerima') }, 400) }) })
Keadaan janji1 ditentukan oleh keadaan janji imbalan, yaitu keadaan janji1 setelah janji imbalan dilaksanakan. Apa manfaatnya? Ini memecahkan masalah panggilan balik neraka .
var janji = wawancara() .lalu(() => { wawancara kembali() }) .lalu(() => { wawancara kembali() }) .lalu(() => { wawancara kembali() }) .menangkap(e => { konsol.log(e) })
Lalu jika status janji yang dikembalikan ditolak, maka tangkapan pertama akan dipanggil, dan tangkapan berikutnya tidak akan dipanggil. Ingat: panggilan yang ditolak adalah tangkapan pertama, dan panggilan yang diselesaikan adalah tangkapan pertama.
Jika janji hanya untuk menyelesaikan panggilan balik neraka, itu terlalu kecil untuk meremehkan janji. Fungsi utama janji adalah untuk memecahkan masalah kontrol proses asinkron. Jika Anda ingin mewawancarai dua perusahaan sekaligus:
function interview() { kembalikan Janji baru((putuskan, tolak) => { setWaktu habis(() => { if (Matematika.acak() > 0,5) { menyelesaikan('sukses') } kalau tidak { tolak(Kesalahan baru('gagal')) } }) }) } janji .all([wawancara(), wawancara()]) .lalu(() => { console.log('senyum') }) // Jika ada perusahaan yang menolak, tangkap saja .menangkap(() => { console.log('menangis') })
Apa sebenarnya sinkronisasi/await itu:
console.log(async function() { kembali 4 }) konsol.log(fungsi() { kembalikan Janji baru((putuskan, tolak) => { menyelesaikan(4) }) })
Hasil cetakannya sama, yaitu async/await hanyalah gula sintaksis untuk janji.
Kita tahu bahwa try catch menangkap kesalahan berdasarkan tumpukan panggilan , dan hanya dapat menangkap kesalahan di atas tumpukan panggilan. Namun jika Anda menggunakan menunggu, Anda dapat menangkap kesalahan di semua fungsi di tumpukan panggilan. Meskipun kesalahan terjadi di tumpukan panggilan loop peristiwa lain, seperti setTimeout.
Setelah mengubah kode wawancara, Anda dapat melihat bahwa kode tersebut telah banyak disederhanakan.
mencoba { tunggu wawancara(1) tunggu wawancara(2) tunggu wawancara(2) } tangkapan(e => { konsol.log(e) })
Bagaimana jika tugas tersebut bersifat paralel?
menunggu Promise.all([interview(1), interview(2)])
Karena I/0 non-pemblokiran dari nodejs, maka perlu menggunakan metode berbasis peristiwa untuk mendapatkan hasil I/O Metode -driven untuk mendapatkan hasil, Anda harus menggunakan pemrograman Asynchronous, seperti fungsi panggilan balik. Jadi bagaimana cara menjalankan fungsi panggilan balik ini untuk mendapatkan hasilnya? Maka Anda perlu menggunakan loop acara.
Perulangan peristiwa adalah fondasi utama untuk mewujudkan fungsi I/O non-pemblokiran dari nodejs. I/O non-pemblokiran dan perulangan peristiwa adalah kemampuan yang disediakan oleh perpustakaan C++ libuv
.
Demonstrasi kode:
const eventloop = { antre: [], lingkaran() { while(ini.antrian.panjang) { const panggilan balik = ini.antrian.shift() panggilan balik() } setTimeout(ini.loop.bind(ini), 50) }, tambahkan(panggilan balik) { this.queue.push(panggilan balik) } } loop peristiwa.loop() setWaktu habis(() => { eventloop.tambahkan(() => { konsol.log('1') }) }, 500) setWaktu habis(() => { eventloop.tambahkan(() => { konsol.log('2') }) }, 800)
setTimeout(this.loop.bind(this), 50)
memastikan bahwa setelah 50 md, ia akan memeriksa apakah ada panggilan balik dalam antrian, dan jika demikian, jalankan. Ini membentuk lingkaran peristiwa.
Tentu saja, kejadian sebenarnya jauh lebih rumit, dan terdapat lebih dari satu antrian. Misalnya, ada antrian operasi file dan antrian waktu.
const eventloop = { antre: [], fsAntrian: [], antrian pengatur waktu: [], lingkaran() { while(ini.antrian.panjang) { const panggilan balik = ini.antrian.shift() panggilan balik() } this.fsQueue.forEach(panggilan balik => { jika (selesai) { panggilan balik() } }) setTimeout(ini.loop.bind(ini), 50) }, tambahkan(panggilan balik) { this.queue.push(panggilan balik) } }
Pertama-tama, kita memahami apa itu I/O non-blocking, yaitu segera melewatkan eksekusi tugas-tugas berikutnya ketika menghadapi I/O, dan tidak akan menunggu hasil I/O. Saat I/O diproses, fungsi pemrosesan peristiwa yang kita daftarkan akan dipanggil, yang disebut dengan event-driven. Pemrograman asinkron diperlukan untuk mengimplementasikan event drive. Pemrograman asinkron adalah tautan terpenting dalam nodejs. Mulai dari fungsi panggilan balik ke janji dan akhirnya ke async/menunggu (menggunakan metode sinkron untuk menulis logika asinkron).