邱敬幃 Pardn Chiu
目錄
標籤
nodejs callback promise async await

Promise 與 Async/Await 的非同步設計

Node.js: JavaScript 的後端魔法 (2)

540次觀看 0收藏 後端開發 Nodejs 非同步設計
非同步程式設計、事件驅動、Promises 以 及Async/Await 是Node.js強大的支柱。
非同步程式設計帶來效能的飆升,事件驅動模型使應用程式更靈活反應。
在這篇內容中,我們將深入解析這些概念。

理解非同步程式設計

非同步程式設計基於事件驅動模型,使得應用程式能夠同時處理多個任務,而不需等待一個操作完成後再執行下一個。這樣的設計方式顯著提高了應用程式的反應速度,尤其適用於處理大量並發請求的場景。

在 Node.js 中,當某個非同步操作開始執行時,不會阻塞後續的程式碼執行,而是將回調函式放入事件循環中,等待操作完成後再被調用。避免了傳統同步編程中的等待時間。例如我們可以使用許多 API 來實現非同步程式設計,如文件讀寫、網路請求等。

然而,非同步程式設計也帶來了一些挑戰,最主要的是處理異常和保持程式碼清晰可讀性。為了解決這些問題,Node.js 引入了 Promise 和 Async/Await 這兩種概念。以 Node.js 中的文件讀寫操作為範例:

  1. const fs = require("fs"); 
  2. console.log("開始讀取文件");
  3. fs.readFile("example.txt", "utf8", (err, data) => { 
  4.     if (err) throw err; 
  5.     //文件讀取完成後會執行以下指令
  6.     console.log(`文件內容:${data}`); 
  7. }); 
  8. //會直接執行以下指令,而不是等待文件讀取完成
  9. console.log("讀取文件操作進行中");

在這個範例中 readFile 函式是非同步的,當文件讀取完成後,回調函式被觸發。而在回調函式之後的 console.log 會立即執行,而不需等待文件讀取操作完成。


Callbacks、Promises、Async/Await

在 Node.js 的非同步程式設計中,Callbacks、Promises 和 Async/Await 是不可忽視的重要概念。這三者代表著不同的非同步處理方式,具有各自的優勢和適用場景。以下將深入探討 Callbacks、Promises 和 Async/Await 的特性,並透過範例演示它們在實際開發中的應用。

Callbacks (回調函式)

Callbacks 是最基本的非同步處理方式,在 Node.js 中被廣泛使用。它的基本思想是將一個函式作為參數傳遞給另一個函式,在後者完成任務後呼叫前者。以下是一個簡單的 Callbacks 例子:

  1. function getDataFromServer(next) {
  2.     /*
  3.      * 用 timer 模擬透過 server 取得資料的情境
  4.      * 在送出請求的 1 秒後取得資料
  5.      */
  6.     setTimeout(() => { 
  7.         console.log("資料取得完成");
  8.         // 傳遞資料內容
  9.         next("資料"); 
  10.     }, 1000); 
  11. }; 
  12. getDataFromServer(data => { 
  13.     // 取得資料後執行下一個指令
  14.     console.log(data); 
  15. });

在這個例子中, getDataFromServer 函式模擬異步操作,並在完成後呼叫 next 函式。 Callbacks 的缺點在於可能產生回調地獄 (Callback Hell),使得程式碼難以維護。

Promises

Promises 是一種更為結構化的非同步處理方式,用於解決 Callbacks 帶來的可讀性問題。 Promises 有三種狀態 pendingfulfilledrejected ,分別表示異步操作未完成、成功完成和失敗。以下是一個 Promises 的範例:

  1. function getDataFromServer() { 
  2.     return new Promise((res, rej) => { 
  3.         setTimeout(() => {
  4.             console.log("資料取得完成"); 
  5.             res("資料"); 
  6.         }, 1000); 
  7.     }); 
  8. }; 
  9. getDataFromServer()
  10.     .then(data => { 
  11.         //取得資料後執行下一個指令
  12.         console.log(data); 
  13.     })
  14.     .catch(err => { 
  15.         console.error(err); 
  16.     });

在這個例子中, getDataFromServer 返回一個 Promise,可以使用 .then 處理成功狀態,使用 .catch 處理失敗狀態。 Promises 使得非同步程式碼更具結構性。

Async/Await:

Async/Await 是建構在 Promises 之上的語法糖,使非同步程式碼更接近同步風格。以下是一個 Async/Await 的範例:

  1. //搭配上async
  2. async function getDataFromServer() { 
  3.     return new Promise((res, rej) => { 
  4.         setTimeout(() => {
  5.             console.log("資料取得完成"); 
  6.             res("資料");
  7.         }, 1000); 
  8.     }); 
  9. }; 
  10. async getData() { 
  11.     //使用await,等待取得資料後才會進行下一步指令
  12.     const data = await getDataFromServer(); 
  13.     console.log(data); 
  14. }; 
  15. getData();

透過 async 標記函式為異步,以及使用 await 等待 Promise 的解析,我們能夠以更簡潔的方式寫出非同步程式碼,提高可讀性。


事件驅動架構

事件驅動架構基於觀察者模式,其中一個對象維護一個訂閱者列表,並在某些事件發生時通知所有訂閱者。在 Node.js 中,這種模式得以實現,使得應用程式能夠有效地處理大量的並發請求。

EventEmitter 的使用

Node.js 核心模塊提供了 EventEmitter 類別,它是實現事件驅動的基礎。以下是一個簡單的範例:

  1. const EventEmitter = require("events").EventEmitter; 
  2. const event = new EventEmitter(); 
  3. event.on("event", function() { 
  4.     console.log("事件"); 
  5. }); 
  6. /*
  7.  * 透過timer設定1秒後數發事件
  8.  */
  9. setTimeout(function() { 
  10.     event.emit("event"); 
  11. }, 1000);

在這個例子中, event 是一個事件發射器,當使用 on 方法註冊了一個事件監聽器後,使用 emit 方法觸發了事件。所有註冊的監聽器都將被呼叫。

HTTP伺服器實際應用

在實際的 Node.js 應用中,事件驅動特別適用於處理 HTTP 請求。以下是一個簡單的 HTTP 伺服器的例子:

  1. const http = require("http");
  2. // 建立一個HTTP伺服器
  3. const server = http.createServer((req, res) => {
  4.     res.send("Hello World");
  5. });
  6. // 註冊伺服器啟動事件
  7. server.on("listening", () => {
  8.     console.log("Server on");
  9. });
  10. // 啟動伺服器
  11. server.listen(3000);

在這個例子中, createServer 方法返回一個具有事件驅動特性的伺服器對象。通過註冊 listening 事件,我們能夠在伺服器啟動時執行額外的邏輯。這種方式使得我們能夠更靈活地處理伺服器的生命週期事件。

事件驅動的優勢

事件驅動架構的一大優勢是元件之間的解耦。不同的元件可以通過事件的發射和監聽來進行通信,而不需要直接引用對方。這使得代碼更容易擴展和維護。


系列文章


相關連結