Fitur baru yang dapat mengubah cara kita menulis
JavaScript adalah bahasa yang sangat fleksibel dan kuat yang membentuk evolusi web modern. Salah satu alasan utama JavaScript begitu dominan dalam pengembangan web adalah perkembangannya yang cepat dan peningkatan yang berkelanjutan.
Satu saran untuk meningkatkan JavaScript adalah sarandisebut "menunggu tingkat atas" (menunggu tingkat atas, "menunggu global"). Tujuan dari proposal ini adalah untuk mengubah modul ES menjadi sesuatu seperti fungsi asinkron. Ini akan memungkinkan modul mendapatkan sumber daya yang siap digunakan dan memblokir modul yang mengimpornya. Modul yang mengimpor sumber daya yang ditunggu hanya akan dapat menjalankan eksekusi kode setelah sumber daya diterima dan disiapkan untuk digunakan.
Proposal ini sedang dalam 3 tahap pertimbangan, jadi fitur ini belum dapat digunakan dalam produksi. Namun, bisa dipastikan dalam waktu dekat sudah pasti akan diimplementasikan.
Jangan khawatir tentang ini. Teruskan membaca. Saya akan menunjukkan kepada Anda bagaimana Anda dapat menggunakan fitur bernama sekarang.
Apa yang salah dengan menunggu normal?
Jika Anda mencoba menggunakan kata kunci await di luar fungsi asinkron, Anda akan mendapatkan kesalahan sintaks. Untuk menghindari hal ini, pengembang menggunakan Ekspresi Fungsi yang Diminta Langsung (IIFE).
await Promise.resolve(console.log("οΈ")); //
(async () => {
await Promise.resolve(console.log("οΈ"))
})();
Masalah yang ditentukan dan solusinya hanyalah puncak gunung es.
Saat bekerja dengan modul ES6, Anda cenderung berurusan dengan banyak contoh mengekspor dan mengimpor nilai. Mari pertimbangkan sebuah contoh:
// library.js
export const sqrt = Math.sqrt;
export const square = (x) => x * x;
export const diagonal = (x, y) => sqrt((square(x) + square(y)));
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("οΈ"));
clearTimeout(timer);
}, ms);
});
// IIFE
(async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
})();
export { squareOutput, diagonalOutput };
Dalam contoh di atas, kami mengekspor dan mengimpor variabel antara library.js dan middleware.js. Anda dapat memberi nama file apa pun yang Anda inginkan.
Fungsi penundaan mengembalikan janji yang diselesaikan setelah penundaan. Karena fungsi ini tidak sinkron, kami menggunakan kata kunci "await" di dalam IIFE untuk "menunggu" hingga selesai. Dalam aplikasi nyata, alih-alih fungsi "penundaan", akan ada panggilan untuk mengambil (permintaan untuk menerima data) atau tugas asinkron lainnya. Setelah menyelesaikan janji, kami menetapkan nilai ke variabel kami. Ini berarti variabel kita tidak akan ditentukan hingga janji diselesaikan.
Di akhir kode, kami mengekspor variabel kami sehingga dapat digunakan dalam kode lain.
Mari kita lihat kode di mana variabel-variabel ini diimpor dan digunakan:
// main.js
import { squareOutput, diagonalOutput } from "./middleware.js";
console.log(squareOutput); // undefined
console.log(diagonalOutput); // undefined
console.log("From Main");
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
Jika Anda menjalankan kode ini, Anda akan mendapatkan tidak terdefinisi dalam dua kasus pertama, dan 169 dan 13 dalam kasus ketiga dan keempat. Mengapa ini terjadi?
Hal ini disebabkan oleh fakta bahwa kami mencoba untuk mendapatkan nilai variabel yang diekspor dari middleware.js di main.js sebelum menjalankan fungsi asinkron. Apakah Anda ingat kami memiliki janji menunggu penyelesaian?
Untuk mengatasi masalah ini, kita perlu memberi tahu modul importing bahwa variabel siap digunakan.
Solusi
Setidaknya ada dua cara untuk mengatasi masalah ini.
1. Ekspor Janji untuk Inisialisasi
Pertama, IIFE dapat diekspor. Kata kunci async menjadikan metode asynchronous, metode seperti itu selalu menghasilkan promise. Inilah sebabnya, pada contoh di bawah ini, IIFE asinkron mengembalikan sebuah promise.
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("οΈ"));
clearTimeout(timer);
}, ms);
});
// , ,
export default (async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
})();
export { squareOutput, diagonalOutput };
Saat mengakses variabel yang diekspor di main.js, Anda dapat menunggu IIFE untuk dieksekusi.
// main.js
import promise, { squareOutput, diagonalOutput } from "./middleware.js";
promise.then(() => {
console.log(squareOutput); // 169
console.log(diagonalOutput); // 169
console.log("From Main");
});
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
Terlepas dari kenyataan bahwa cuplikan ini menyelesaikan masalah, ini mengarah ke masalah lain.
- Saat menggunakan template yang ditentukan, Anda harus mencari janji yang diinginkan
- Jika modul lain juga menggunakan variabel "squareOutput" dan "diagonalOutput", kita harus memastikan bahwa IIFE diekspor kembali
Ada juga cara lain.
2. Resolusi janji IIFE dengan variabel yang diekspor
Dalam hal ini, alih-alih mengekspor variabel satu per satu, kami mengembalikannya dari IIFE asinkron kami. Ini memungkinkan file "main.js" untuk menunggu janji diselesaikan dan mengambil nilainya.
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("οΈ"));
clearTimeout(timer);
}, ms);
});
//
export default (async () => {
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
return { squareOutput, diagonalOutput };
})();
// main.js
import promise from "./middleware.js";
promise.then(({ squareOutput, diagonalOutput }) => {
console.log(squareOutput); // 169
console.log(diagonalOutput); // 169
console.log("From Main");
});
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
Namun, solusi ini juga memiliki beberapa kekurangan.
Menurut saran, βpola ini memiliki kelemahan serius karena memerlukan pemfaktoran ulang yang substansial dari sumber sumber daya terkait menjadi templat yang lebih dinamis, dan menempatkan sebagian besar badan modul dalam callback .then () untuk memungkinkan modul dinamis. Ini menunjukkan kemunduran yang signifikan dalam hal kemampuan analisis statis, kemampuan untuk diuji, ergonomi, dan lainnya dibandingkan dengan modul ES2015. "
Bagaimana menunggu "global" memecahkan masalah ini?
menunggu tingkat atas memungkinkan sistem modular untuk menangani penyelesaian janji dan bagaimana mereka berinteraksi satu sama lain.
// middleware.js
import { square, diagonal } from "./library.js";
console.log("From Middleware");
let squareOutput;
let diagonalOutput;
const delay = (ms) => new Promise((resolve) => {
const timer = setTimeout(() => {
resolve(console.log("οΈ"));
clearTimeout(timer);
}, ms);
});
// "" await
await delay(1000);
squareOutput = square(13);
diagonalOutput = diagonal(12, 5);
export { squareOutput, diagonalOutput };
// main.js
import { squareOutput, diagonalOutput } from "./middleware.js";
console.log(squareOutput); // 169
console.log(diagonalOutput); // 13
console.log("From Main");
const timer1 = setTimeout(() => {
console.log(squareOutput);
clearTimeout(timer1);
}, 2000); // 169
const timer2 = setTimeout(() => {
console.log(diagonalOutput);
clearTimeout(timer2);
}, 2000); // 13
Tidak ada pernyataan di main.js yang dijalankan sampai promise di middleware.js diselesaikan. Ini adalah solusi yang jauh lebih bersih daripada solusi.
Catatan
Menunggu global hanya bekerja dengan modul ES. Dependensi yang digunakan harus ditentukan secara eksplisit. Contoh di bawah ini dari repositori proposal menunjukkan hal ini dengan baik.
// x.mjs
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");
// y.mjs
console.log("Y");
// z.mjs
import "./x.mjs";
import "./y.mjs";
// X1
// Y
// X2
Cuplikan ini tidak akan menampilkan X1, X2, Y ke konsol, seperti yang Anda duga, karena x dan y adalah modul yang terpisah, tidak terkait satu sama lain.
Saya sangat menyarankan Anda mempelajari bagian FAQ proposal untuk pemahaman yang lebih baik tentang fitur yang dimaksud.
Penerapan
V8
Anda dapat menguji fitur ini sekarang.
Untuk melakukan ini, buka direktori tempat Chrome berada di komputer Anda. Pastikan semua tab browser ditutup. Buka terminal dan masukkan perintah berikut:
chrome.exe --js-flags="--harmony-top-level-await"
Anda juga dapat mencoba fitur ini di Node.js. Baca panduan ini untuk mengetahui lebih lanjut.
Modul ES
Pastikan untuk menambahkan atribut "type" ke tag "script" dengan nilai "module".
<script type="module" src="./index.js"></script>
Harap diperhatikan bahwa tidak seperti skrip biasa, modul ES6 mengikuti kebijakan Shared Origin (Single Source) (SOP) dan Resource Sharing (CORS). Oleh karena itu, lebih baik bekerja dengan mereka di server.
Kasus penggunaan
Menurut proposal tersebut, kasus penggunaan untuk menunggu "global" adalah sebagai berikut:
Jalur ketergantungan dinamis
const strings = await import(`/i18n/${navigator.language}`);
Ini memungkinkan modul menggunakan nilai runtime untuk menghitung jalur dependensi dan dapat berguna untuk memisahkan kode pengembangan / produksi, internasionalisasi, memisahkan kode berdasarkan runtime (browser, Node.js), dll.
Menginisialisasi sumber daya
const connection = await dbConnector()
Ini membantu modul untuk mendapatkan sumber daya yang siap digunakan dan memunculkan pengecualian saat modul tidak dapat digunakan. Pendekatan ini dapat digunakan sebagai jaring pengaman, seperti yang ditunjukkan di bawah ini.
Opsi fallback
Contoh di bawah ini menunjukkan bagaimana await "global" dapat digunakan untuk memuat dependensi dengan implementasi fallback. Jika impor dari CDN A gagal, impor dari CDN B dilakukan:
let jQuery;
try {
jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.example.com/jQuery');
}
Kritik
Rich Harris telah menyusun daftar kritik menunggu tingkat atas. Ini termasuk yang berikut:
- "Global" menunggu dapat memblokir eksekusi kode
- Penantian "global" dapat memblokir akuisisi sumber daya
- Kurangnya dukungan untuk modul CommonJS
Jawaban atas komentar ini diberikan dalam proposal FAQ:
- Karena node anak (modul) memiliki kemampuan untuk dieksekusi, pada akhirnya tidak ada pemblokiran kode
- Tunggu "global" digunakan selama fase eksekusi grafik modul. Pada tahap ini, semua sumber daya diterima dan ditautkan, jadi tidak ada risiko pemblokiran akuisisi sumber daya
- tingkat atas menunggu terbatas pada modul ES6. Dukungan untuk modul CommonJS, seperti skrip biasa, pada awalnya tidak direncanakan
Sekali lagi, saya sangat merekomendasikan membaca FAQ proposal.
Saya harap saya dapat menjelaskan esensi proposal yang dimaksud dengan cara yang mudah dipahami. Apakah Anda akan menggunakan kesempatan ini? Bagikan pendapat Anda di komentar.