Bagaimana memulai dengan cepat dengan VUE3.0: Pelajari
Meskipun JavaScript adalah thread tunggal, loop peristiwa menggunakan kernel sistem sebanyak mungkin sehingga memungkinkan Node.js melakukan operasi I/O non-pemblokiran. Meskipun sebagian besar kernel modern adalah multi-thread, mereka dapat menangani tugas-tugas multi-thread di latar belakang. Ketika tugas selesai, kernel memberitahu Node.js, dan kemudian panggilan balik yang sesuai akan ditambahkan ke loop untuk dieksekusi. Artikel ini akan memperkenalkan topik ini secara lebih rinci.
Ketika Node.js memulai eksekusi, loop peristiwa pertama-tama akan diinisialisasi, memproses skrip masukan yang disediakan (atau memasukkannya ke dalam REPL, yang tidak tercakup dalam dokumen ini). Ini akan melakukan panggilan API asinkron, menjadwalkan pengatur waktu, atau memanggil proses.nextTick(), dan kemudian mulai memproses loop peristiwa.
Gambar berikut menunjukkan urutan eksekusi loop peristiwa yang disederhanakan
┌─────────────────────────┐ ┌─>│ pengatur waktu │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ panggilan balik tertunda │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ menganggur, siapkan │ │ └─────────────┬─────────────┘ ┌─────────────────┐ │ ┌─────────────┴─────────────┐ │ masuk: │ │ │ jajak pendapat │<─────┤ koneksi, │ │ └─────────────┬──────────────┘ │ data, dll. │ │ ┌─────────────┴─────────────┐ └──────────────────┘ │ │ periksa │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ tutup panggilan balik │ └─────────────────────────────┘Setiap
kotak mewakili tahapan loop peristiwa.
Namun, setiap tahap memiliki eksekusi panggilan balik antrian FIFO , setiap tahap dijalankan dengan caranya sendiri. Secara umum, ketika loop peristiwa memasuki suatu tahap, ia akan melakukan operasi apa pun di tahap saat ini dan mulai mengeksekusi panggilan balik dalam antrian tahap saat ini hingga antrian tersebut sepenuhnya dikonsumsi atau dieksekusi. datanya maksimal. Ketika antrian habis atau mencapai ukuran maksimumnya, loop peristiwa berpindah ke fase berikutnya.
Pengatur waktuDi setiap proses loop peristiwa, Node .js memeriksa untuk melihat apakah ia sedang menunggu I/O asinkron dan pengatur waktu, dan jika tidak mematikan
waktu menentukan titik kritis di mana panggilan balik akan dieksekusi, bukan waktu yang diinginkan untuk mengeksekusinya. Pengatur waktu akan dijalankan sesegera mungkin setelah waktu berlalu yang ditentukan, namun, penjadwalan sistem operasi atau callback lainnya dapat menunda eksekusi.
Secara teknis, fase polling menentukan kapan callback dijalankan.
Misalnya, Anda menyetel timer untuk dieksekusi setelah 100 ms, tetapi skrip Anda membaca file secara asinkron dan membutuhkan waktu 95 ms
const fs = require('fs') ; fungsi someAsyncOperation(panggilan balik) { // Asumsikan ini memerlukan waktu 95 md untuk menyelesaikannya fs.readFile('/path/ke/file', panggilan balik); } const timeoutScheduled = Tanggal.sekarang(); setWaktu habis(() => { const delay = Tanggal.sekarang() - batas waktu terjadwal; console.log(`${delay}ms telah berlalu sejak saya dijadwalkan`); }, 100); // lakukan someAsyncOperation yang membutuhkan waktu 95 ms untuk menyelesaikannya someAsyncOperation(() => { const startCallback = Tanggal.sekarang(); // lakukan sesuatu yang memerlukan waktu 10 md... while (Tanggal.sekarang() - startCallback < 10) { // tidak melakukan apa pun } });
Saat loop peristiwa memasuki fase polling, ada antrian kosong (fs.readFile() belum selesai), sehingga akan menunggu sisa milidetik hingga ambang batas waktu tercepat tercapai .readFile() telah selesai membaca file dan memerlukan waktu 10 ms untuk ditambahkan ke fase polling dan menyelesaikan eksekusi. Saat panggilan balik selesai, tidak ada panggilan balik dalam antrean yang akan dieksekusi, dan loop peristiwa kembali ke fase pengatur waktu. untuk menjalankan panggilan balik pengatur waktu. Dalam contoh ini, Anda akan melihat bahwa pengatur waktu ditunda selama 105 ms sebelum dijalankan.
Untuk mencegah fase polling memblokir loop peristiwa, libuv (perpustakaan bahasa C yang mengimplementasikan loop peristiwa dan semua perilaku asinkron pada platform) juga memiliki fase polling. max stop polling lebih banyak event
Fase ini mengeksekusi callback untuk operasi sistem tertentu (seperti tipe kesalahan TCP). Misalnya, beberapa sistem *nix ingin menunggu hingga kesalahan dilaporkan jika soket TCP menerima ECONNREFUSED saat mencoba menyambung. Ini akan dimasukkan ke dalam antrean untuk dieksekusi selama fase panggilan balik yang tertunda.
Fase polling memiliki dua fungsi utama
Ketika loop event memasuki fase polling dan tidak ada pengatur waktu, dua hal berikut akan terjadi
Setelah antrian polling kosong, loop acara akan mendeteksi apakah pengatur waktu telah kedaluwarsa. Jika demikian, loop peristiwa akan mencapai tahap pengatur waktu dan menjalankan panggilan balik pengatur waktu,
tahap ini. Jika fase polling menjadi tidak aktif dan skrip telah dimasukkan ke dalam antrean menggunakan setImmediate(), perulangan peristiwa dapat melanjutkan ke fase pemeriksaan alih-alih menunggu.
setImmediate() sebenarnya adalah pengatur waktu khusus yang berjalan dalam fase terpisah dari perulangan peristiwa. Ia menggunakan API libuv untuk menjadwalkan callback yang akan dieksekusi setelah fase polling selesai.
Biasanya, saat kode dijalankan, loop peristiwa akhirnya mencapai fase polling, di mana ia menunggu koneksi masuk, permintaan, dll. Namun, jika callback dijadwalkan menggunakan setImmediate() dan fase polling menjadi tidak aktif, callback akan berakhir dan dilanjutkan dengan fase pemeriksaan alih-alih menunggu acara polling.
Jika soket atau operasi ditutup secara tiba-tiba (misalnya socket.destroy()), event close akan dikirim ke tahap ini, jika tidak maka akan dikirim melalui process.nextTick() setImmediate
setImmediate() dan setTimeout() serupa, namun berperilaku berbeda bergantung pada kapan keduanya dipanggil
Urutan eksekusi setiap callback bergantung pada urutan pemanggilannya Dalam lingkungan ini, jika modul yang sama dipanggil pada waktu yang sama, waktunya akan dibatasi oleh kinerja proses (ini juga akan dipengaruhi oleh aplikasi lain yang berjalan pada mesin ini)
. , jika kita tidak menjalankan skrip berikut di I/O, meskipun dipengaruhi oleh kinerja proses, urutan eksekusi kedua pengatur waktu ini tidak dapat ditentukan:
// timeout_vs_immediate.js setWaktu habis(() => { console.log('batas waktu'); }, 0); set Segera(() => { console.log('segera'); });
$ batas waktu simpul_vs_immediate.js batas waktu segera $ simpul batas waktu_vs_immediate.js segera timeout
Namun, jika Anda berpindah ke loop I/O, callback langsung akan selalu dieksekusi terlebih dahulu
// timeout_vs_immediate.js const fs = memerlukan('fs'); fs.readFile(__namafile, () => { setWaktu habis(() => { console.log('batas waktu'); }, 0); set Segera(() => { console.log('segera'); }); });
$ batas waktu simpul_vs_immediate.js segera batas waktu $ simpul batas waktu_vs_immediate.js segeraKeuntungan dari
timeout
setImmediate dibandingkan setTimeout adalah setImmediate akan selalu dieksekusi terlebih dahulu sebelum timer mana pun di I/O, berapa pun jumlah timer yang ada.
Meskipun process.nextTick() adalah bagian dari API asinkron, Anda mungkin memperhatikan bahwa ini tidak muncul dalam diagram. Ini karena process.nextTick() bukan bagian dari teknologi loop peristiwa. operasi saat ini dijalankan Setelah selesai, nextTickQueue akan dieksekusi terlepas dari tahap perulangan peristiwa saat ini. Di sini, operasi didefinisikan sebagai transformasi dari pengendali C/C++ yang mendasarinya dan menangani JavaScript yang perlu dijalankan. Menurut diagram, Anda dapat memanggil process.nextTick() pada tahap mana pun. Semua callback yang diteruskan ke process.nextTick() akan dieksekusi sebelum loop peristiwa melanjutkan eksekusi . process.nextTick() "membuat kelaparan" I/O Anda, yang mencegah loop peristiwa memasuki fase polling.
Mengapa situasi ini termasuk dalam Node.js? Karena filosofi desain Node.js adalah bahwa API harus selalu asynchronous meskipun tidak harus demikian, lihat cuplikan
function apiCall(arg, callback)berikut
jika (typeof arg !== 'string') proses kembali.nextTick( panggilan balik, new TypeError('argumen harus berupa string') ); }
Cuplikan melakukan pemeriksaan parameter dan jika salah, cuplikan meneruskan kesalahan ke callback. API baru-baru ini diperbarui untuk memungkinkan meneruskan parameter ke process.nextTick() yang memungkinkannya menerima parameter apa pun yang diteruskan setelah callback sebagai argumen ke callback, sehingga Anda tidak perlu menumpuk fungsi.
Apa yang kita lakukan adalah meneruskan kesalahan kembali ke pengguna, tetapi hanya jika kita mengizinkan sisa kode pengguna untuk dieksekusi. Dengan menggunakan process.nextTick(), kami memastikan bahwa apiCall() selalu menjalankan callbacknya setelah kode pengguna lainnya dan sebelum mengizinkan perulangan peristiwa untuk dilanjutkan. Untuk mencapai hal ini, tumpukan panggilan JS diizinkan untuk dilepas dan kemudian panggilan balik yang disediakan segera dijalankan, yang memungkinkan seseorang melakukan panggilan rekursif ke process.nextTick() tanpa mencapai RangeError: Ukuran tumpukan panggilan maksimum terlampaui pada v8.