非同步程式設計、事件驅動、Promises 以 及Async/Await 是Node.js強大的支柱。
非同步程式設計帶來效能的飆升,事件驅動模型使應用程式更靈活反應。
在這篇內容中,我們將深入解析這些概念。
非同步程式設計基於事件驅動模型,使得應用程式能夠同時處理多個任務,而不需等待一個操作完成後再執行下一個。這樣的設計方式顯著提高了應用程式的反應速度,尤其適用於處理大量並發請求的場景。
在 Node.js 中,當某個非同步操作開始執行時,不會阻塞後續的程式碼執行,而是將回調函式放入事件循環中,等待操作完成後再被調用。避免了傳統同步編程中的等待時間。例如我們可以使用許多 API 來實現非同步程式設計,如文件讀寫、網路請求等。
然而,非同步程式設計也帶來了一些挑戰,最主要的是處理異常和保持程式碼清晰可讀性。為了解決這些問題,Node.js 引入了 Promise 和 Async/Await 這兩種概念。以 Node.js 中的文件讀寫操作為範例:
- const fs = require("fs");
- console.log("開始讀取文件");
- fs.readFile("example.txt", "utf8", (err, data) => {
- if (err) throw err;
- //文件讀取完成後會執行以下指令
- console.log(`文件內容:${data}`);
- });
- //會直接執行以下指令,而不是等待文件讀取完成
- console.log("讀取文件操作進行中");
在這個範例中 readFile
函式是非同步的,當文件讀取完成後,回調函式被觸發。而在回調函式之後的 console.log
會立即執行,而不需等待文件讀取操作完成。
在 Node.js 的非同步程式設計中,Callbacks、Promises 和 Async/Await 是不可忽視的重要概念。這三者代表著不同的非同步處理方式,具有各自的優勢和適用場景。以下將深入探討 Callbacks、Promises 和 Async/Await 的特性,並透過範例演示它們在實際開發中的應用。
Callbacks 是最基本的非同步處理方式,在 Node.js 中被廣泛使用。它的基本思想是將一個函式作為參數傳遞給另一個函式,在後者完成任務後呼叫前者。以下是一個簡單的 Callbacks 例子:
- function getDataFromServer(next) {
- /*
- * 用 timer 模擬透過 server 取得資料的情境
- * 在送出請求的 1 秒後取得資料
- */
- setTimeout(() => {
- console.log("資料取得完成");
- // 傳遞資料內容
- next("資料");
- }, 1000);
- };
- getDataFromServer(data => {
- // 取得資料後執行下一個指令
- console.log(data);
- });
在這個例子中, getDataFromServer
函式模擬異步操作,並在完成後呼叫 next
函式。 Callbacks 的缺點在於可能產生回調地獄 (Callback Hell),使得程式碼難以維護。
Promises 是一種更為結構化的非同步處理方式,用於解決 Callbacks 帶來的可讀性問題。 Promises 有三種狀態 pending
、 fulfilled
和 rejected
,分別表示異步操作未完成、成功完成和失敗。以下是一個 Promises 的範例:
- function getDataFromServer() {
- return new Promise((res, rej) => {
- setTimeout(() => {
- console.log("資料取得完成");
- res("資料");
- }, 1000);
- });
- };
- getDataFromServer()
- .then(data => {
- //取得資料後執行下一個指令
- console.log(data);
- })
- .catch(err => {
- console.error(err);
- });
在這個例子中, getDataFromServer
返回一個 Promise,可以使用 .then
處理成功狀態,使用 .catch
處理失敗狀態。 Promises 使得非同步程式碼更具結構性。
Async/Await 是建構在 Promises 之上的語法糖,使非同步程式碼更接近同步風格。以下是一個 Async/Await 的範例:
- //搭配上async
- async function getDataFromServer() {
- return new Promise((res, rej) => {
- setTimeout(() => {
- console.log("資料取得完成");
- res("資料");
- }, 1000);
- });
- };
- async getData() {
- //使用await,等待取得資料後才會進行下一步指令
- const data = await getDataFromServer();
- console.log(data);
- };
- getData();
透過 async
標記函式為異步,以及使用 await
等待 Promise 的解析,我們能夠以更簡潔的方式寫出非同步程式碼,提高可讀性。
事件驅動架構基於觀察者模式,其中一個對象維護一個訂閱者列表,並在某些事件發生時通知所有訂閱者。在 Node.js 中,這種模式得以實現,使得應用程式能夠有效地處理大量的並發請求。
Node.js 核心模塊提供了 EventEmitter 類別,它是實現事件驅動的基礎。以下是一個簡單的範例:
- const EventEmitter = require("events").EventEmitter;
- const event = new EventEmitter();
- event.on("event", function() {
- console.log("事件");
- });
- /*
- * 透過timer設定1秒後數發事件
- */
- setTimeout(function() {
- event.emit("event");
- }, 1000);
在這個例子中, event
是一個事件發射器,當使用 on
方法註冊了一個事件監聽器後,使用 emit
方法觸發了事件。所有註冊的監聽器都將被呼叫。
在實際的 Node.js 應用中,事件驅動特別適用於處理 HTTP 請求。以下是一個簡單的 HTTP 伺服器的例子:
- const http = require("http");
- // 建立一個HTTP伺服器
- const server = http.createServer((req, res) => {
- res.send("Hello World");
- });
- // 註冊伺服器啟動事件
- server.on("listening", () => {
- console.log("Server on");
- });
- // 啟動伺服器
- server.listen(3000);
在這個例子中, createServer
方法返回一個具有事件驅動特性的伺服器對象。通過註冊 listening
事件,我們能夠在伺服器啟動時執行額外的邏輯。這種方式使得我們能夠更靈活地處理伺服器的生命週期事件。
事件驅動架構的一大優勢是元件之間的解耦。不同的元件可以通過事件的發射和監聽來進行通信,而不需要直接引用對方。這使得代碼更容易擴展和維護。