在 TypeScript 中的時候,基本上一定會寫到非同步(異步)執行的操作來提高性能,但由於 JavaScript 本身是單執行緒,所以通常會使用 Web Workers 來執行並行任務。但在這種情境下,確保型別的安全性變得更加更加地重要了。
在 TypeScript 中,當我們使用 Promises 或 async/await 時,經常希望知道最終的結果會是什麼型別。例如:
function fetchData(): Promise<string> {
return new Promise((resolve, reject) => {
// 模擬非同步操作
setTimeout(() => {
resolve("Data fetched!");
}, 1000);
});
}
這裡,fetchData
函數返回一個 Promise
,當解決時,它將回傳一個字串。TypeScript 可以在編譯 ( tsc ) 時確保當我們使用函式並處理它的結果時,確保得到一個字串。
async/await
是一種更直觀、更具可讀性的方式來處理 Promises。同樣,我們可以使用 TypeScript 來確保我們的非同步函數具有正確的返回型別:
async function fetchDataAsync(): Promise<number> {
const result = await someOtherAsyncFunction(); // 假設這個函數返回 Promise<number>
return result * 2;
}
在上述情況下,fetchDataAsync
回傳的是一個數字的 Promise
,這是由 TypeScript 在編譯時期去做確定的。
使用 async/await 的一個常見問題是錯誤處理。一個常見的解決方案是使用一種稱為 "Option" 或 "Result" 型別的模式。
type Result<T, E = Error> = {
value?: T;
error?: E;
}
async function safeFetchDataAsync(): Promise<Result<number>> {
try {
const data = await fetchDataAsync();
return { value: data };
} catch (error) {
return { error: error };
}
}
這種方法的好處是,不僅可以定義成功的結果或錯誤,還可以通過型別系統確定返回的內容。
當有多個獨立的非同步操作需要同時執行時,Promise.all
是一個很有用的工具。使用 TypeScript,我們可以確定當所有 Promises 完成時,我們得到一個型別確定的陣列。
let promise1: Promise<string> = fetchData();
let promise2: Promise<number> = fetchDataAsync();
let results = await Promise.all([promise1, promise2]);
// TypeScript 知道 results 的型別是 [string, number]
最後,Web Workers 是一種允許您在背景執行緒中運行 JavaScript 代碼,而不干擾主執行緒的機制。但是主執行緒與 worker 之間的主要通訊方式為訊息傳遞( message passing )。而 TypeScript 可以提供很好的型別安全。
Web Workers 是瀏覽器上提供的 API ,雖然它的設計在安全性很好,但是不是我們之前認為的安全性,而是在記體上的安全性。畢竟並行的操作很有可能讓記憶體操作上帶來很大的隱憂。不過現在的我應該不需要考慮到這一塊XD
那首先我們先定義型別,用來表示訊息的內容型別。
type WorkerCommand = {
task: 'compute' | 'sort' | 'fetchData';
data: any;
}
type WorkerResponse = {
status: 'success' | 'error';
result?: any;
error?: string;
}
這能讓我們確定主執行序發送到 worker 和 worker 返回的訊息型別,或稱之為形狀。
在主執行緒上我們可以創建一個 worker 並使用它做
const worker = new Worker('path-to-worker-file.js');
worker.postMessage({
task: 'compute',
data: { /* some data */ }
});
worker.onmessage = (event: MessageEvent<WorkerResponse>) => {
if(event.data.status === 'success') {
console.log(event.data.result);
} else {
console.error(event.data.error);
}
};
然後在 worker.js 中我們可去執行類似這樣的事情。
self.addEventListener('message', (event: MessageEvent<WorkerCommand>) => {
switch(event.data.task) {
case 'compute':
// ... do some computations
self.postMessage({
status: 'success',
result: computedData
});
break;
// Handle other tasks...
default:
self.postMessage({
status: 'error',
error: 'Unknown task'
});
}
});
也就是說,我們在執行的過程需要的訊息和處理完成回傳的訊息我們都經過的型別上的定義。這種方法的好處是,如果我們試圖發送不正確的消息格式或期望從 worker 收到的消息不匹配,TypeScript 將會在編譯時通知我們。這大大減少了由於錯誤的消息格式導致的錯誤。