Halo, Habr! Saya mempersembahkan untuk Anda terjemahan artikel
“Memahami Asynchronous JavaScript” oleh Sukhjinder Arora.
Dari penulis terjemahan: Saya harap terjemahan artikel ini akan membantu Anda membiasakan diri dengan sesuatu yang baru dan bermanfaat. Jika artikelnya membantu Anda, maka jangan malas dan berterima kasih pada penulis aslinya. Saya tidak berpura-pura menjadi penerjemah profesional, saya baru mulai menerjemahkan artikel dan saya akan senang dengan umpan balik informatif.JavaScript adalah bahasa pemrograman single-threaded di mana hanya satu hal yang dapat dieksekusi pada suatu waktu. Artinya, dalam satu utas, mesin JavaScript hanya dapat memproses 1 pernyataan pada satu waktu.
Meskipun bahasa single-threaded membuat kode penulisan lebih mudah karena Anda tidak perlu khawatir tentang masalah konkurensi, itu juga berarti Anda tidak akan dapat melakukan operasi panjang seperti mengakses jaringan tanpa memblokir utas utama.
Kirim permintaan API untuk beberapa data. Bergantung pada situasinya, server mungkin membutuhkan waktu untuk memproses permintaan Anda, sementara eksekusi aliran utama akan diblokir karena halaman web Anda akan berhenti merespons permintaan itu.
Di sinilah JavaScript asinkron berperan. Menggunakan JavaScript asynchrony (callback, janji, dan async / menunggu) Anda dapat melakukan permintaan jaringan yang panjang tanpa memblokir utas utama.
Meskipun tidak perlu mempelajari semua konsep ini untuk menjadi pengembang JavaScript yang baik, itu berguna untuk mengetahuinya.
Jadi, tanpa basa-basi, mari kita mulai.
Bagaimana cara kerja javascript sinkron?
Sebelum kita mempelajari pekerjaan asinkron JavaScript, mari kita terlebih dahulu memahami bagaimana kode sinkron berjalan di dalam mesin JavaScript. Sebagai contoh:
const second = () => { console.log('Hello there!'); } const first = () => { console.log('Hi there!'); second(); console.log('The End'); } first();
Untuk memahami bagaimana kode di atas dieksekusi di dalam mesin JavaScript, kita perlu memahami konsep konteks eksekusi dan tumpukan panggilan (juga dikenal sebagai tumpukan eksekusi).
Konteks eksekusi
Konteks eksekusi adalah konsep abstrak dari lingkungan di mana kode dievaluasi dan dieksekusi. Setiap kali kode dieksekusi dalam JavaScript, ia berjalan dalam konteks eksekusi.
Kode fungsi dieksekusi di dalam konteks eksekusi fungsi, dan kode global, pada gilirannya, dieksekusi di dalam konteks eksekusi global. Setiap fungsi memiliki konteks eksekusi sendiri.
Tumpukan panggilan
Tumpukan panggilan adalah tumpukan dengan struktur LIFO (Last in, First Out, first use), yang digunakan untuk menyimpan semua konteks eksekusi yang dibuat selama eksekusi kode.
JavaScript hanya memiliki satu tumpukan panggilan, karena ini adalah bahasa pemrograman single-threaded. Struktur LIFO berarti bahwa elemen hanya dapat ditambahkan dan dihapus dari bagian atas tumpukan.
Sekarang mari kita kembali ke potongan kode di atas dan mencoba memahami bagaimana mesin JavaScript melakukan itu.
const second = () => { console.log('Hello there!'); } const first = () => { console.log('Hi there!'); second(); console.log('The End'); } first();

Jadi apa yang terjadi di sini?
Ketika kode mulai dieksekusi, konteks eksekusi global dibuat (direpresentasikan sebagai
main () ) dan ditambahkan ke bagian atas tumpukan panggilan. Ketika panggilan ke fungsi
pertama () ditemui, itu juga ditambahkan ke bagian atas tumpukan.
Selanjutnya,
console.log ('Hai di sana!') Ditempatkan di bagian atas tumpukan panggilan, setelah eksekusi dihapus dari tumpukan. Setelah itu, kita memanggil fungsi
kedua () , sehingga ditempatkan di bagian atas tumpukan.
console.log ('Halo di sana!') ditambahkan ke bagian atas tumpukan dan dihapus darinya setelah penyelesaian eksekusi. Fungsi
kedua () selesai, itu juga dihapus dari tumpukan.
console.log ('The End') ditambahkan ke bagian atas tumpukan dan dihapus di bagian akhir. Setelah itu, fungsi
pertama () berakhir dan juga dihapus dari tumpukan.
Eksekusi program berakhir, sehingga konteks panggilan global (
main () ) dihapus dari stack.
Bagaimana cara kerja JavaScript asinkron?
Sekarang kita memiliki pemahaman dasar tentang tumpukan panggilan dan cara kerja JavaScript sinkron, mari kembali ke JavaScript asinkron.
Apa itu pemblokiran?
Mari kita asumsikan bahwa kita sedang memproses pemrosesan gambar atau permintaan jaringan secara serempak. Sebagai contoh:
const processImage = (image) => { console.log('Image processed'); } const networkRequest = (url) => { return someData; } const greeting = () => { console.log('Hello World'); } processImage(logo.jpg); networkRequest('www.somerandomurl.com'); greeting();
Pemrosesan gambar dan permintaan jaringan membutuhkan waktu. Ketika fungsi
processImage () dipanggil, eksekusi akan memakan waktu, tergantung pada ukuran gambar.
Ketika fungsi
processImage () selesai, ia dihapus dari tumpukan. Setelah itu, fungsi
networkRequest () dipanggil dan ditambahkan ke stack. Ini lagi akan memakan waktu sebelum menyelesaikan eksekusi.
Pada akhirnya, ketika fungsi
networkRequest () dijalankan, fungsi
ucapan () dipanggil, karena hanya berisi metode
console.log , dan metode ini biasanya cepat, fungsi
ucapan () akan mengeksekusi dan berakhir secara instan.
Seperti yang Anda lihat, kita perlu menunggu fungsi (seperti
processImage () atau
networkRequest () ) selesai. Ini berarti bahwa fungsi tersebut memblokir tumpukan panggilan atau utas utama. Akibatnya, kami tidak dapat melakukan operasi lain hingga kode di atas dijalankan.
Jadi apa solusinya?
Solusi paling sederhana adalah fungsi callback asinkron. Kami menggunakannya untuk membuat kode kami tidak terblokir. Sebagai contoh:
const networkRequest = () => { setTimeout(() => { console.log('Async Code'); }, 2000); }; console.log('Hello World'); networkRequest();
Di sini saya menggunakan metode
setTimeout untuk mensimulasikan permintaan jaringan. Harap ingat bahwa
setTimeout bukan bagian dari mesin JavaScript, itu adalah bagian dari apa yang disebut web API (di browser) dan C / C ++ API (dalam node.js).
Untuk memahami bagaimana kode ini dieksekusi, kita perlu berurusan dengan beberapa konsep lagi, seperti loop acara dan antrian panggilan balik (juga dikenal sebagai antrian tugas atau antrian pesan).

Perulangan acara, API web, dan antrian pesan / antrian tugas bukan bagian dari mesin JavaScript, melainkan bagian dari runtime JavaScript JavaScript atau runtime JavaScript di Nodejs (dalam kasus Nodejs). Di Nodejs, API web diganti dengan C / C ++ API.
Sekarang, mari kita kembali ke kode di atas dan melihat apa yang terjadi dalam kasus eksekusi asinkron.
const networkRequest = () => { setTimeout(() => { console.log('Async Code'); }, 2000); }; console.log('Hello World'); networkRequest(); console.log('The End');

Ketika kode di atas dimuat ke browser,
console.log ('Hello World') ditambahkan ke tumpukan dan dihapus dari itu setelah penyelesaian eksekusi. Selanjutnya, panggilan ke fungsi
networkRequest () ditemui , ditambahkan ke bagian atas tumpukan.
Selanjutnya, fungsi
setTimeout () dipanggil dan ditempatkan di bagian atas tumpukan. Fungsi
setTimeout () memiliki 2 argumen: 1) fungsi panggilan balik dan 2) waktu dalam milidetik.
setTimeout () memulai penghitung waktu selama 2 detik di lingkungan API web. Pada titik ini,
setTimeout () selesai dan dihapus dari tumpukan. Setelah itu,
console.log ('The End') ditambahkan ke stack, dieksekusi dan dihapus dari itu setelah selesai.
Sementara itu, timer telah kedaluwarsa, sekarang callback ditambahkan ke antrian pesan. Tetapi panggilan balik tidak dapat segera dieksekusi, dan di sinilah siklus pemrosesan peristiwa memasuki proses.
Perulangan acara
Tugas dari loop acara adalah untuk melacak tumpukan panggilan dan menentukan apakah itu kosong atau tidak. Jika tumpukan panggilan kosong, maka loop acara terlihat di antrian pesan untuk melihat apakah ada panggilan balik yang menunggu untuk diselesaikan.
Dalam kasus kami, antrian pesan berisi satu panggilan balik, dan tumpukan eksekusi kosong. Oleh karena itu, loop peristiwa menambahkan panggilan balik ke bagian atas tumpukan.
Setelah
console.log ('Kode Async') ditambahkan ke bagian atas tumpukan, dieksekusi dan dihapus darinya. Pada titik ini, panggilan balik selesai dan dihapus dari tumpukan, dan program sepenuhnya selesai.
Acara DOM
Antrian pesan juga berisi panggilan balik dari acara DOM, seperti klik dan acara keyboard. Sebagai contoh:
document.querySelector('.btn').addEventListener('click',(event) => { console.log('Button Clicked'); });
Dalam kasus peristiwa DOM, pengendali peristiwa dikelilingi oleh API web, menunggu peristiwa tertentu (dalam hal ini, klik), dan ketika peristiwa ini terjadi, fungsi panggilan balik ditempatkan dalam antrian pesan, menunggu pelaksanaannya.
Kami mempelajari bagaimana panggilan balik tidak sinkron dan peristiwa DOM dieksekusi, yang menggunakan antrian pesan untuk menyimpan panggilan balik yang menunggu untuk dieksekusi.
ES6 MicroTask Queue
Catatan penulis terjemahan: Dalam artikel tersebut, penulis menggunakan antrian pesan / tugas dan antrian pekerjaan / mikro-taks, tetapi jika Anda menerjemahkan antrian tugas dan antrian pekerjaan, maka secara teori ternyata hal yang sama. Saya berbicara dengan penulis terjemahan dan memutuskan untuk menghilangkan konsep antrian pekerjaan. Jika Anda memiliki pemikiran tentang ini, maka saya menunggu Anda di komentar
Tautkan ke terjemahan artikel dengan janji dari penulis yang sama
ES6 memperkenalkan konsep antrian mikrotask, yang digunakan oleh Promises in JavaScript. Perbedaan antara antrian pesan dan antrian mikrotask adalah bahwa antrian mikrotask memiliki prioritas lebih tinggi daripada antrian pesan, yang berarti bahwa "janji" di dalam antrian mikrotask akan dieksekusi lebih awal daripada panggilan balik dalam antrian pesan.
Sebagai contoh:
console.log('Script start'); setTimeout(() => { console.log('setTimeout'); }, 0); new Promise((resolve, reject) => { resolve('Promise resolved'); }).then(res => console.log(res)) .catch(err => console.log(err)); console.log('Script End');
Kesimpulan:
Script start Script End Promise resolved setTimeout
Seperti yang Anda lihat, "janji" dijalankan sebelum
setTimeout , semua ini karena respons dari "janji" disimpan di dalam antrian microstask, yang memiliki prioritas lebih tinggi daripada antrian pesan.
Mari kita lihat contoh berikut, kali ini 2 "janji" dan 2
setTimeout :
console.log('Script start'); setTimeout(() => { console.log('setTimeout 1'); }, 0); setTimeout(() => { console.log('setTimeout 2'); }, 0); new Promise((resolve, reject) => { resolve('Promise 1 resolved'); }).then(res => console.log(res)) .catch(err => console.log(err)); new Promise((resolve, reject) => { resolve('Promise 2 resolved'); }).then(res => console.log(res)) .catch(err => console.log(err)); console.log('Script End');
Kesimpulan:
Script start Script End Promise 1 resolved Promise 2 resolved setTimeout 1 setTimeout 2
Dan lagi, kedua "janji" kami dieksekusi sebelum callback di dalam
setTimeout , karena loop pemrosesan acara menganggap tugas dari antrian mikrotask lebih penting daripada tugas dari antrian pesan / antrian tugas.
Jika “Janji” lain muncul dari antrian mikrotask selama pelaksanaan tugas, maka akan ditambahkan ke akhir antrian ini dan dieksekusi sebelum panggilan balik dari antrian pesan, dan tidak peduli berapa lama mereka menunggu eksekusi.
Sebagai contoh:
console.log('Script start'); setTimeout(() => { console.log('setTimeout'); }, 0); new Promise((resolve, reject) => { resolve('Promise 1 resolved'); }).then(res => console.log(res)); new Promise((resolve, reject) => { resolve('Promise 2 resolved'); }).then(res => { console.log(res); return new Promise((resolve, reject) => { resolve('Promise 3 resolved'); }) }).then(res => console.log(res)); console.log('Script End');
Kesimpulan:
Script start Script End Promise 1 resolved Promise 2 resolved Promise 3 resolved setTimeout
Dengan demikian, semua tugas dari antrian mikrotask akan selesai sebelum tugas dari antrian pesan. Yaitu, loop pemrosesan acara pertama-tama akan menghapus antrian mikrotask, dan baru kemudian ia akan mulai mengeksekusi callback dari antrian pesan.
Kesimpulan
Jadi, kami mempelajari cara kerja JavaScript dan konsep asinkron: tumpukan panggilan, loop acara, antrian pesan / antrian tugas, dan antrian mikrotask yang membentuk runtime JavaScript