最近開始不斷學習實踐 JavaScript ,出於效能測試的敏感,首先研究了 JavaScript 的非同步程式設計實踐,目前看跟之前學過的 Java 和 Go 都不太一樣。使用的語法上相對更加複雜,也可能因為我實踐得還太少。
非同步程式設計
非同步程式設計是一種在程式執行過程中,不阻塞主執行緒的任務處理方式。相較於同步程式設計,非同步程式設計允許程式在等待某個任務(例如網路請求或檔案讀寫)完成的同時,繼續執行其他操作。這種方式極大提高了程式的效率,尤其在處理大量 I/O 操作時,表現尤為突出。
在 JavaScript 中,非同步程式設計尤為重要,原因在於它的單執行緒特性。JavaScript 在瀏覽器環境下執行時,只有一個主執行緒負責執行程式碼。如果程式中某個任務需要較長時間才能完成(比如網路請求、資料庫查詢等),而沒有采用非同步處理方式,主執行緒將會被阻塞,導致整個頁面的互動變得遲緩甚至無響應。
為了避免這種情況,JavaScript 提供了多種非同步處理方式,如回撥函式、Promise、async/await等。透過這些機制,JavaScript 能夠在處理耗時任務時,不阻塞主執行緒,保持頁面的流暢性和響應性。
回撥函式(Callback)
回撥函式是指作為引數傳遞給另一個函式,並在該函式執行完畢後呼叫的函式。在 JavaScript 的非同步程式設計中,回撥函式是最早且最基礎的實現方式之一。當某個非同步操作(如網路請求或定時器)完成時,JavaScript 執行時環境會呼叫提供的回撥函式,繼續執行後續的邏輯。這種模式允許我們在非同步任務完成後進行處理,而不阻塞主執行緒。
在 JavaScript 中,回撥函式透過結合瀏覽器或 Node.js 的事件迴圈機制來實現非同步行為。單單使用 JavaScript 本身無法實現非同步,而是透過將任務交給瀏覽器或 Node.js 的執行時(如定時器、I/O 操作、網路請求等)來處理,等這些任務完成後,再透過回撥函式把結果傳回給 JavaScript 主執行緒繼續執行。這整個過程就是實現非同步的關鍵。
示例
function fetchData(callback) {
console.log("Fetching data...");
setTimeout(() => {// 模擬耗時操作
// 模擬耗時操作
const data = "Hello FunTester!";
callback(data); // 呼叫回撥函式,傳遞資料
}, 1000);
}
function processData(data) {
console.log("Data received:", data);// 處理資料的邏輯
}
console.info("start --------");
fetchData(processData);// 呼叫 fetchData 函式,傳入回撥函式
console.info("end --------");
在這個例子中,fetchData
模擬了一個非同步資料獲取的操作,執行完成後呼叫回撥函式 processData
來處理獲取到的資料。控制檯列印:
start --------
Fetching data...
end --------
Data received: Hello FunTester!
Promise
Promise 是一種用於處理非同步操作的物件,它代表了一個非同步操作的最終完成(或失敗)及其結果值。Promise 提供了更清晰、更可讀的方式來管理多個非同步操作,特別是當它們相互依賴時。相比回撥函式,Promise 能避免回撥地獄,使程式碼結構更加扁平化和易於維護。
Promise 的三種狀態
- Pending(待定):初始狀態,表示非同步操作尚未完成,也沒有結果。
- Fulfilled(已完成):非同步操作成功完成,並返回一個值。
- Rejected(已拒絕):非同步操作失敗,並返回一個原因。
在 Promise 的生命週期中,它只能從 pending
狀態轉移到 fulfilled
或 rejected
狀態,並且一旦狀態發生變化,結果就不會再改變。
Promise 的基本用法
一個 Promise 物件會接受一個函式作為引數,該函式本身又有兩個引數:resolve
和 reject
。當非同步操作成功時,呼叫 resolve
來將 Promise 狀態改為 fulfilled
;當操作失敗時,呼叫 reject
將狀態改為 rejected
。
.then()
和 .catch()
的用法
-
.then()
:用於處理 Promise 成功完成(fulfilled
)後的結果。你可以在.then()
中執行後續操作,鏈式呼叫。 -
.catch()
:用於捕獲 Promise 的錯誤(rejected
),並處理異常情況。
Promise 示例
const fetchData = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 模擬成功或失敗
if (success) {// 成功, 呼叫 resolve, 並傳遞資料
resolve("hello, FunTester");
} else {// 失敗, 呼叫 reject, 並傳遞錯誤資訊
reject("sorry, error occurred");
}
});
});
};
console.info("start--------------");
// 使用 Promise 來處理非同步操作
fetchData()
.then((data) => {// 成功時的處理
console.log("received:", data);
return data.length;
})
.then((age) => {// 成功時的處理
console.log("length:", age);
})
.catch((error) => {// 失敗時的處理
console.error("Error:", error);
});
console.info("end--------------");
控制檯列印:
start--------------
end--------------
received: hello, FunTester
length: 16
下面是一些小小的 tips:
- Promise 提供了一種更加簡潔、優雅的方式來處理非同步操作,避免回撥地獄。
- 它有三種狀態:
pending
(待定)、fulfilled
(已完成)、rejected
(已拒絕)。 - 使用
.then()
處理成功結果,使用.catch()
捕獲和處理錯誤。 - 多個非同步操作可以鏈式呼叫,提升了程式碼的可讀性和維護性。
async
/await
async
/await
是 ES2017(ES8)引入的語法,提供了一種更加簡潔的方式來處理非同步程式碼,使其看起來更像同步程式碼。這種方式可以減少 Promise 鏈的複雜性,提升程式碼的可讀性和維護性。async
/await
實際上是基於 Promises 之上的語法糖。
透過使用 async
和 await
,我們可以編寫看起來像同步執行的程式碼,但它在內部仍然是非同步的。這種寫法可以避免 Promise 鏈式呼叫的繁瑣結構,將非同步任務的執行流順序化,邏輯更加清晰。
async
/await
的基本用法
-
async
函式:宣告一個async
函式,它會自動返回一個 Promise。即使沒有顯式返回 Promise,async
函式預設會把返回值封裝成一個 Promise。 -
await
表示式:用於等待一個 Promise 的結果。它暫停async
函式的執行,直到 Promise 解決(resolve)或拒絕(reject),然後繼續執行後續程式碼。
示例:使用 async
/await
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {// 模擬非同步操作
const success = true;
if (success) {
resolve("hello funtester");
} else {
reject("sorry, error");
}
}, 1000);
});
};
async function getData() {
try {
const data = await fetchData(); // 等待 Promise 解決
console.log("Data received:", data);
} catch (error) {
console.error("Error:", error); // 處理錯誤
}
}
console.info("start----------------");
let data = getData();
console.log("data:", data);
console.info("end----------------");
控制檯列印:
start----------------
data: Promise { <pending> }
end----------------
Data received: hello funtester
在使用 async/await
時,錯誤處理的方式類似於同步程式碼中捕獲異常。我們可以使用 try/catch
塊來處理非同步操作中的異常,這種方式比在每個 .then()
末尾新增 .catch()
更加直觀。
async/await
基於 Promise
需要注意的是,async
/await
並不是替代 Promise 的機制,而是基於 Promise 進行的更高階抽象。它們簡化了 Promise 的鏈式呼叫,消除了巢狀結構。可以把 await
視作一個暫停點,等待 Promise 完成(無論是成功還是失敗),使得非同步程式碼的處理更符合程式的執行邏輯。
非同步程式設計的重要性
在 JavaScript 中,非同步程式設計至關重要,因為 JavaScript 執行在單執行緒環境中,尤其是在瀏覽器和 Node.js 等平臺中。單執行緒意味著同一時間只能執行一個任務。如果程式碼中的某些操作(如網路請求、檔案讀取或定時任務)需要較長的執行時間,而 JavaScript 只支援同步程式設計的話,整個執行緒將被阻塞,使用者介面會變得卡頓或無響應。因此,非同步程式設計透過不阻塞主執行緒來解決這個問題,使得 JavaScript 能夠高效處理 I/O 密集型任務,保持應用的流暢執行。
當然 JavaScript 非同步程式設計的內容還有其他高階用法,比如學習 Promise.all、Promise.race 等,將來等我實踐充足再來分享。
FunTester 原創精華
- 混沌工程、故障測試、Web 前端
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go
- 白盒、工具、爬蟲、UI 自動化
- 理論、感悟、影片