Apakah Saatnya Menggunakan Node 8?
Diterbitkan: 2022-03-11Node 8 keluar! Faktanya, Node 8 sekarang telah keluar cukup lama untuk melihat beberapa penggunaan dunia nyata yang solid. Itu datang dengan mesin V8 baru yang cepat dan dengan fitur-fitur baru, termasuk async/await, HTTP/2, dan async hooks. Tapi apakah itu siap untuk proyek Anda? Mari kita cari tahu!
Catatan editor: Anda mungkin menyadari bahwa Node 10 (bernama kode Dubnium ) juga keluar. Kami memilih untuk fokus pada Node 8 ( Carbon ) karena dua alasan: (1) Node 10 baru saja memasuki fase long-term support (LTS), dan (2) Node 8 menandai iterasi yang lebih signifikan daripada Node 10. .
Performa di Node 8 LTS
Kami akan mulai dengan melihat peningkatan kinerja dan fitur baru dari rilis yang luar biasa ini. Salah satu area peningkatan utama adalah pada mesin JavaScript Node.
Apa sebenarnya mesin JavaScript itu?
Mesin JavaScript mengeksekusi dan mengoptimalkan kode. Ini bisa berupa penerjemah standar atau kompiler just-in-time (JIT) yang mengkompilasi JavaScript ke bytecode. Mesin JS yang digunakan oleh Node.js semuanya adalah kompiler JIT, bukan juru bahasa.
Mesin V8
Node.js telah menggunakan mesin JavaScript Google Chrome V8 , atau hanya V8 , sejak awal. Beberapa rilis Node digunakan untuk menyinkronkan dengan versi V8 yang lebih baru. Tapi berhati-hatilah untuk tidak membingungkan V8 dengan Node 8 karena kami membandingkan versi V8 di sini.
Ini mudah tersandung, karena dalam konteks perangkat lunak kami sering menggunakan "v8" sebagai bahasa gaul atau bahkan bentuk pendek resmi untuk "versi 8", jadi beberapa mungkin menggabungkan "Node V8" atau "Node.js V8" dengan "NodeJS 8" ,” tapi kami telah menghindari ini di seluruh artikel ini untuk membantu menjaga semuanya tetap jelas: V8 akan selalu berarti mesin, bukan versi Node.js.
V8 Rilis 5
Node 6 menggunakan rilis V8 5 sebagai mesin JavaScript-nya. (Beberapa titik rilis pertama dari Node 8 juga menggunakan rilis V8 5, tetapi mereka menggunakan rilis titik V8 yang lebih baru daripada Node 6.)
Kompiler
Rilis V8 5 dan sebelumnya memiliki dua kompiler:
- Full-codegen adalah kompiler JIT yang sederhana dan cepat tetapi menghasilkan kode mesin yang lambat.
- Crankshaft adalah kompiler JIT kompleks yang menghasilkan kode mesin yang dioptimalkan.
Utas
Jauh di lubuk hati, V8 menggunakan lebih dari satu jenis utas:
- Utas utama mengambil kode, mengompilasinya, lalu mengeksekusinya.
- Utas sekunder mengeksekusi kode sementara utas utama mengoptimalkan kode.
- Utas profiler menginformasikan waktu proses tentang metode yang tidak berkinerja baik. Crankshaft kemudian mengoptimalkan metode ini.
- Utas lainnya mengelola pengumpulan sampah.
Proses Kompilasi
Pertama, kompiler Full-codegen mengeksekusi kode JavaScript. Saat kode sedang dieksekusi, utas profiler mengumpulkan data untuk menentukan metode mana yang akan dioptimalkan mesin. Di utas lain, Crankshaft mengoptimalkan metode ini.
Masalah
Pendekatan yang disebutkan di atas memiliki dua masalah utama. Pertama, kompleks secara arsitektur. Kedua, kode mesin yang dikompilasi menghabiskan lebih banyak memori. Jumlah memori yang dikonsumsi tidak tergantung pada berapa kali kode dieksekusi. Bahkan kode yang hanya berjalan sekali juga memakan banyak memori.
V8 Rilis 6
Versi Node pertama yang menggunakan mesin V8 release 6 adalah Node 8.3.
Dalam rilis 6, tim V8 membuat Ignition dan TurboFan untuk mengurangi masalah ini. Ignition dan TurboFan masing-masing menggantikan Full-codegen dan CrankShaft.
Arsitektur baru lebih mudah dan mengkonsumsi lebih sedikit memori.
Ignition mengkompilasi kode JavaScript ke bytecode alih-alih kode mesin, menghemat banyak memori. Setelah itu, TurboFan, compiler pengoptimal, menghasilkan kode mesin yang dioptimalkan dari bytecode ini.
Peningkatan Kinerja Spesifik
Mari kita pergi melalui area di mana kinerja di Node 8.3+ berubah relatif terhadap versi Node yang lebih lama.
Membuat Objek
Membuat objek sekitar lima kali lebih cepat di Node 8.3+ daripada di Node 6.
Ukuran Fungsi:
Mesin V8 memutuskan apakah suatu fungsi harus dioptimalkan berdasarkan beberapa faktor. Salah satu faktornya adalah ukuran fungsi. Fungsi kecil dioptimalkan, sedangkan fungsi panjang tidak.
Bagaimana Ukuran Fungsi Dihitung?
Crankshaft pada mesin V8 lama menggunakan “character count” untuk menentukan ukuran fungsi. Spasi dan komentar dalam suatu fungsi mengurangi kemungkinannya untuk dioptimalkan. Saya tahu ini mungkin mengejutkan Anda, tetapi saat itu, komentar dapat mengurangi kecepatan sekitar 10%.
Di Node 8.3+, karakter yang tidak relevan seperti spasi dan komentar tidak merusak kinerja fungsi. Kenapa tidak?
Karena TurboFan baru tidak menghitung karakter untuk menentukan ukuran fungsi. Sebagai gantinya, ia menghitung simpul pohon sintaksis abstrak (AST), sehingga secara efektif ia hanya mempertimbangkan instruksi fungsi yang sebenarnya . Menggunakan Node 8.3+, Anda dapat menambahkan komentar dan spasi sebanyak yang Anda inginkan.
Argumen Pengubah Array
Fungsi reguler dalam JavaScript membawa objek argument
seperti Array
implisit.
Apa Arti Array
-like?
Objek arguments
bertindak seperti array . Ini memiliki properti length
tetapi tidak memiliki metode bawaan Array
seperti forEach
dan map
.
Inilah cara kerja objek arguments
:
function foo() { console.log(arguments[0]); // Expected output: a console.log(arguments[1]); // Expected output: b console.log(arguments[2]); // Expected output: c } foo("a", "b", "c");
Jadi bagaimana kita bisa mengonversi objek arguments
menjadi array? Dengan menggunakan Array.prototype.slice.call(arguments)
singkat.
function test() { const r = Array.prototype.slice.call(arguments); console.log(r.map(num => num * 2)); } test(1, 2, 3); // Expected output: [2, 4, 6]
Array.prototype.slice.call(arguments)
merusak kinerja di semua versi Node. Oleh karena itu, menyalin kunci melalui for
bekerja lebih baik:
function test() { const r = []; for (index in arguments) { r.push(arguments[index]); } console.log(r.map(num => num * 2)); } test(1, 2, 3); // Expected output [2, 4, 6]
for
loop agak rumit, bukan? Kita bisa menggunakan operator spread, tapi lambat di Node 8.2 ke bawah:
function test() { const r = [...arguments]; console.log(r.map(num => num * 2)); } test(1, 2, 3); // Expected output [2, 4, 6]
Situasi telah berubah di Node 8.3+. Sekarang spread dieksekusi jauh lebih cepat, bahkan lebih cepat daripada for-loop.
Aplikasi Parsial (Currying) dan Binding
Currying adalah memecah fungsi yang mengambil beberapa argumen menjadi serangkaian fungsi di mana setiap fungsi baru hanya membutuhkan satu argumen.
Katakanlah kita memiliki fungsi add
sederhana. Versi kari dari fungsi ini membutuhkan satu argumen, num1
. Ini mengembalikan fungsi yang mengambil argumen lain num2
dan mengembalikan jumlah num1
dan num2
:
function add(num1, num2) { return num1 + num2; } add(4, 6); // returns 10 function curriedAdd(num1) { return function(num2) { return num1 + num2; }; } const add5 = curriedAdd(5); add5(3); // returns 8
Metode bind
mengembalikan fungsi kari dengan sintaks terser.
function add(num1, num2) { return num1 + num2; } const add5 = add.bind(null, 5); add5(3); // returns 8
Jadi bind
itu luar biasa, tetapi lambat di versi Node yang lebih lama. Di Node 8.3+, bind
jauh lebih cepat dan Anda dapat menggunakannya tanpa mengkhawatirkan kinerja apa pun.
Eksperimen
Beberapa percobaan telah dilakukan untuk membandingkan kinerja Node 6 dengan Node 8 pada level tinggi. Perhatikan bahwa ini dilakukan pada Node 8.0 sehingga tidak termasuk peningkatan yang disebutkan di atas yang khusus untuk Node 8.3+ berkat pemutakhiran V8 rilis 6-nya.
Waktu rendering server di Node 8 adalah 25% lebih sedikit daripada di Node 6. Dalam proyek besar, jumlah instance server dapat dikurangi dari 100 menjadi 75. Ini mencengangkan. Menguji serangkaian 500 tes di Node 8 adalah 10% lebih cepat. Pembuatan Webpack 7% lebih cepat. Secara umum, hasil menunjukkan peningkatan kinerja yang nyata di Node 8.
Fitur Node 8
Kecepatan bukan satu-satunya peningkatan di Node 8. Ini juga membawa beberapa fitur baru yang berguna—mungkin yang paling penting, async/await .
Async/Menunggu di Node 8
Panggilan balik dan janji biasanya digunakan untuk menangani kode asinkron dalam JavaScript. Callback terkenal karena menghasilkan kode yang tidak dapat dipelihara. Mereka telah menyebabkan kekacauan (dikenal secara khusus sebagai panggilan balik neraka ) di komunitas JavaScript. Janji menyelamatkan kami dari neraka panggilan balik untuk waktu yang lama, tetapi mereka masih kekurangan kebersihan kode sinkron. Async/await adalah pendekatan modern yang memungkinkan Anda menulis kode asinkron yang terlihat seperti kode sinkron.
Dan sementara async/await dapat digunakan di versi Node sebelumnya, diperlukan pustaka dan alat eksternal—misalnya, prapemrosesan tambahan melalui Babel. Sekarang tersedia secara asli, di luar kotak.
Saya akan berbicara tentang beberapa kasus di mana async/await lebih unggul dari janji konvensional.
bersyarat
Bayangkan Anda mengambil data dan Anda akan menentukan apakah panggilan API baru diperlukan berdasarkan payload . Lihat kode di bawah ini untuk melihat bagaimana hal ini dilakukan melalui pendekatan "janji konvensional".
const request = () => { return getData().then(data => { if (!data.car) { return fetchForCar(data.id).then(carData => { console.log(carData); return carData; }); } else { console.log(data); return data; } }); };
Seperti yang Anda lihat, kode di atas sudah terlihat berantakan, hanya dari satu ekstra conditional. Async/menunggu melibatkan lebih sedikit sarang:
const request = async () => { const data = await getData(); if (!data.car) { const carData = await fetchForCar(data); console.log(carData); return carData; } else { console.log(data); return data; } };
Penanganan Kesalahan
Async/await memberi Anda akses untuk menangani kesalahan sinkron dan asinkron dalam try/catch. Katakanlah Anda ingin mengurai JSON yang berasal dari panggilan API asinkron. Satu percobaan/tangkap dapat menangani kesalahan penguraian dan kesalahan API.
const request = async () => { try { console.log(await getData()); } catch (err) { console.log(err); } };
Nilai Menengah
Bagaimana jika sebuah janji membutuhkan argumen yang harus diselesaikan dari janji lain? Ini berarti bahwa panggilan asinkron harus dilakukan secara seri.
Menggunakan janji konvensional, Anda mungkin berakhir dengan kode seperti ini:
const request = () => { return fetchUserData() .then(userData => { return fetchCompanyData(userData); }) .then(companyData => { return fetchRetiringPlan(userData, companyData); }) .then(retiringPlan => { const retiringPlan = retiringPlan; }); };
Async/await bersinar dalam kasus ini, di mana panggilan asinkron berantai diperlukan:
const request = async () => { const userData = await fetchUserData(); const companyData = await fetchCompanyData(userData); const retiringPlan = await fetchRetiringPlan(userData, companyData); };
Async secara Paralel
Bagaimana jika Anda ingin memanggil lebih dari satu fungsi asinkron secara paralel? Pada kode di bawah ini, kita akan menunggu fetchHouseData
untuk diselesaikan, lalu panggil fetchCarData
. Meskipun masing-masing adalah independen dari yang lain, mereka diproses secara berurutan. Anda akan menunggu dua detik untuk menyelesaikan kedua API. Ini tidak bagus.

function fetchHouseData() { return new Promise(resolve => setTimeout(() => resolve("Mansion"), 1000)); } function fetchCarData() { return new Promise(resolve => setTimeout(() => resolve("Ferrari"), 1000)); } async function action() { const house = await fetchHouseData(); // Wait one second const car = await fetchCarData(); // ...then wait another second. console.log(house, car, " in series"); } action();
Pendekatan yang lebih baik adalah memproses panggilan asinkron secara paralel. Periksa kode di bawah ini untuk mendapatkan gambaran tentang bagaimana ini dicapai di async/menunggu.
async function parallel() { houseDataPromise = fetchHouseData(); carDataPromise = fetchCarData(); const house = await houseDataPromise; // Wait one second for both const car = await carDataPromise; console.log(house, car, " in parallel"); } parallel();
Memproses panggilan ini secara paralel membuat Anda menunggu hanya satu detik untuk kedua panggilan.
Fungsi Pustaka Inti Baru
Node 8 juga menghadirkan beberapa fungsi inti baru.
Salin File
Sebelum Node 8, untuk menyalin file, kami biasa membuat dua aliran dan menyalurkan data dari satu ke yang lain. Kode di bawah ini menunjukkan bagaimana aliran baca menyalurkan data ke aliran tulis. Seperti yang Anda lihat, kodenya berantakan untuk tindakan sederhana seperti menyalin file.
const fs = require('fs'); const rd = fs.createReadStream('sourceFile.txt'); rd.on('error', err => { console.log(err); }); const wr = fs.createWriteStream('target.txt'); wr.on('error', err => { console.log(err); }); wr.on('close', function(ex) { console.log('File Copied'); }); rd.pipe(wr);
Di Node 8 fs.copyFile
dan fs.copyFileSync
adalah pendekatan baru untuk menyalin file dengan lebih sedikit kerumitan.
const fs = require("fs"); fs.copyFile("firstFile.txt", "secondFile.txt", err => { if (err) { console.log(err); } else { console.log("File copied"); } });
Janji dan Callbackify
util.promisify
mengubah fungsi biasa menjadi fungsi asinkron. Perhatikan bahwa fungsi yang dimasukkan harus mengikuti gaya panggilan balik Node.js yang umum. Ini harus mengambil panggilan balik sebagai argumen terakhir, yaitu, (error, payload) => { ... }
.
const { promisify } = require('util'); const fs = require('fs'); const readFilePromisified = promisify(fs.readFile); const file_path = process.argv[2]; readFilePromisified(file_path) .then((text) => console.log(text)) .catch((err) => console.log(err));
Seperti yang Anda lihat, util.promisify
telah mengonversi fs.readFile
ke fungsi async.
Di sisi lain, Node.js hadir dengan util.callbackify
. util.callbackify
adalah kebalikan dari util.promisify
: Ini mengubah fungsi async menjadi fungsi gaya callback Node.js.
destroy
Fungsi untuk Dapat Dibaca dan Dapat Ditulis
Fungsi destroy
di Node 8 adalah cara terdokumentasi untuk menghancurkan/menutup/membatalkan aliran yang dapat dibaca atau ditulis:
const fs = require('fs'); const file = fs.createWriteStream('./big.txt'); file.on('error', errors => { console.log(errors); }); file.write(`New text.\n`); file.destroy(['First Error', 'Second Error']);
Kode di atas menghasilkan pembuatan file baru bernama big.txt
(jika belum ada) dengan teks New text.
.
Fungsi Readable.destroy
dan Writeable.destroy
di Node 8 memancarkan close
event dan opsional error
event— destroy
tidak selalu berarti ada yang salah.
Operator Penyebaran
Operator spread (alias ...
) bekerja di Node 6, tetapi hanya dengan array dan iterable lainnya:
const arr1 = [1,2,3,4,5,6] const arr2 = [...arr1, 9] console.log(arr2) // expected output: [1,2,3,4,5,6,9]
Di Node 8, objek juga dapat menggunakan operator spread:
const userCarData = { type: 'ferrari', color: 'red' }; const userSettingsData = { lastLoggedIn: '12/03/2019', featuresPlan: 'premium' }; const userData = { ...userCarData, name: 'Youssef', ...userSettingsData }; console.log(userData); /* Expected output: { type: 'ferrari', color: 'red', name: 'Youssef', lastLoggedIn: '12/03/2019', featuresPlan: 'premium' } */
Fitur Eksperimental di Node 8 LTS
Fitur eksperimental tidak stabil, dapat dihentikan, dan dapat diperbarui seiring waktu. Jangan gunakan salah satu fitur ini dalam produksi hingga menjadi stabil.
Kait Asinkron
Kait asinkron melacak masa pakai sumber daya asinkron yang dibuat di dalam Node melalui API.
Pastikan Anda memahami loop acara sebelum melangkah lebih jauh dengan hook async. Video ini mungkin bisa membantu. Kait asinkron berguna untuk men-debug fungsi asinkron. Mereka memiliki beberapa aplikasi; salah satunya adalah jejak tumpukan kesalahan untuk fungsi async.
Lihat kode di bawah ini. Perhatikan bahwa console.log
adalah fungsi async. Jadi itu tidak dapat digunakan di dalam kait async. fs.writeSync
digunakan sebagai gantinya.
const asyncHooks = require('async_hooks'); const fs = require('fs'); const init = (asyncId, type, triggerId) => fs.writeSync(1, `${type} \n`); const asyncHook = asyncHooks.createHook({ init }); asyncHook.enable();
Tonton video ini untuk mengetahui lebih banyak tentang async hooks. Khusus untuk panduan Node.js, artikel ini membantu mengungkap kaitan asinkron melalui aplikasi ilustratif.
Modul ES6 di Node 8
Node 8 sekarang mendukung modul ES6, memungkinkan Anda untuk menggunakan sintaks ini:
import { UtilityService } from './utility_service';
Untuk menggunakan modul ES6 di Node 8, Anda perlu melakukan hal berikut.
- Tambahkan
--experimental-modules
ke baris perintah - Ganti nama ekstensi file dari
.js
menjadi.mjs
HTTP/2
HTTP/2 adalah pembaruan terbaru untuk protokol HTTP yang tidak sering diperbarui, dan Node 8.4+ mendukungnya secara asli dalam mode eksperimental. Ini lebih cepat, lebih aman, dan lebih efisien daripada pendahulunya, HTTP/1.1. Dan Google menyarankan Anda menggunakannya. Tapi apa lagi yang dilakukannya?
Multiplexing
Di HTTP/1.1, server hanya dapat mengirim satu respons per koneksi pada satu waktu. Dalam HTTP/2, server dapat mengirim lebih dari satu respons secara paralel.
Server Dorong
Server dapat mendorong beberapa tanggapan untuk satu permintaan klien. Mengapa ini bermanfaat? Ambil aplikasi web sebagai contoh. Secara konvensional,
- Klien meminta dokumen HTML.
- Klien menemukan sumber daya yang dibutuhkan dari dokumen HTML.
- Klien mengirimkan permintaan HTTP untuk setiap sumber daya yang diperlukan. Misalnya, klien mengirimkan permintaan HTTP untuk setiap sumber daya JS dan CSS yang disebutkan dalam dokumen.
Fitur server-push memanfaatkan fakta bahwa server sudah mengetahui semua sumber daya tersebut. Server mendorong sumber daya tersebut ke klien. Jadi untuk contoh aplikasi web, server mendorong semua sumber daya setelah klien meminta dokumen awal. Ini mengurangi latensi.
Prioritas
Klien dapat menetapkan skema prioritas untuk menentukan seberapa penting setiap respons yang diperlukan. Server kemudian dapat menggunakan skema ini untuk memprioritaskan alokasi memori, CPU, bandwidth, dan sumber daya lainnya.
Menghilangkan Kebiasaan Buruk Lama
Karena HTTP/1.1 tidak mengizinkan multiplexing, beberapa pengoptimalan dan solusi digunakan untuk menutupi kecepatan lambat dan pemuatan file. Sayangnya, teknik ini menyebabkan peningkatan konsumsi RAM dan rendering tertunda:
- Sharding domain: Beberapa subdomain digunakan sehingga koneksi tersebar dan diproses secara paralel.
- Menggabungkan file CSS dan JavaScript untuk mengurangi jumlah permintaan.
- Peta Sprite: Menggabungkan file gambar untuk mengurangi permintaan HTTP.
- Inlining: CSS dan JavaScript ditempatkan di HTML secara langsung untuk mengurangi jumlah koneksi.
Sekarang dengan HTTP/2, Anda dapat melupakan teknik ini dan fokus pada kode Anda.
Tapi Bagaimana Anda Menggunakan HTTP/2?
Sebagian besar browser mendukung HTTP/2 hanya melalui koneksi SSL yang aman. Artikel ini dapat membantu Anda mengonfigurasi sertifikat yang ditandatangani sendiri. Tambahkan file .crt
dan file .key
yang dihasilkan dalam direktori bernama ssl
. Kemudian, tambahkan kode di bawah ini ke file bernama server.js
.
Ingatlah untuk menggunakan flag --expose-http2
di baris perintah untuk mengaktifkan fitur ini. Yaitu perintah run untuk contoh kita adalah node server.js --expose-http2
.
const http2 = require('http2'); const path = require('path'); const fs = require('fs'); const PORT = 3000; const secureServerOptions = { cert: fs.readFileSync(path.join(__dirname, './ssl/server.crt')), key: fs.readFileSync(path.join(__dirname, './ssl/server.key')) }; const server = http2.createSecureServer(secureServerOptions, (req, res) => { res.statusCode = 200; res.end('Hello from Toptal'); }); server.listen( PORT, err => err ? console.error(err) : console.log(`Server listening to port ${PORT}`) );
Tentu saja, Node 8, Node 9, Node 10, dll. masih mendukung HTTP 1.1 yang lama—dokumentasi resmi Node.js pada transaksi HTTP standar tidak akan basi untuk waktu yang lama. Tetapi jika Anda ingin menggunakan HTTP/2, Anda bisa masuk lebih dalam dengan panduan Node.js ini.
Jadi, Haruskah Saya Menggunakan Node.js 8 Pada Akhirnya?
Node 8 hadir dengan peningkatan kinerja dan fitur baru seperti async/await, HTTP/2, dan lainnya. Eksperimen ujung-ke-ujung telah menunjukkan bahwa Node 8 sekitar 25% lebih cepat daripada Node 6. Ini mengarah pada penghematan biaya yang substansial. Jadi untuk proyek greenfield, tentu saja! Tetapi untuk proyek yang sudah ada, haruskah Anda memperbarui Node?
Itu tergantung pada apakah Anda perlu mengubah banyak kode yang ada. Dokumen ini mencantumkan semua perubahan yang melanggar Node 8 jika Anda berasal dari Node 6. Ingatlah untuk menghindari masalah umum dengan menginstal ulang semua paket npm
proyek Anda menggunakan versi Node 8 terbaru. Selain itu, selalu gunakan versi Node.js yang sama pada mesin pengembangan seperti pada server produksi. Semoga berhasil!
- Mengapa Saya Menggunakan Node.js? Tutorial Kasus per Kasus
- Men-debug Kebocoran Memori di Aplikasi Node.js
- Membuat REST API Aman di Node.js
- Pengkodean Demam Kabin: Tutorial Back-end Node.js