Javascript 的非同步操作(二)
async/await
async/await 是一種更簡潔的語法,用於處理 Promise。async 關鍵字用於定義一個返回 Promise 的函式,而 await 關鍵字用於等待 Promise 的解決(或拒絕)。
如果想用 async/await 改寫前面打 API 的範例二:
// 定義一個函式,使用 fetch 來獲取使用者數據
const fetchUserData = async () => {
try {
// 使用 await 等待 Promise 解析
const response = await fetch('https://api.example.com/user');
// 檢查響應的狀態碼
if (!response.ok) {
throw new Error('無法獲取使用者數據');
}
// 使用 await 解析 JSON 數據
const userData = await response.json();
console.log('成功獲取使用者數據:', userData);
return userData;
} catch (error) {
// 處理錯誤
console.error('API 請求失敗:', error.message);
throw error; // 可以選擇拋出錯誤供外部處理
}
};
async/await 小練習
在 JavaScript 中,一個非同步函式可以返回一個 Promise 物件。由於 async/await 較前篇文章的 Promise 來說寫起來更簡潔,所以我希望能用 async/await 為主來寫非同步程式,那下面會有兩個 Easy 的 LeetCode 題,主要是讓大家理解說寫非同步程式時,可以回傳 Promise resolve 的值,也可以回傳 Promise 來使用。
LeetCode: Add Two Promises
第一題就是前篇文章的最後一個例子,需要使用 Promise.all 來處理兩個 Promise resolve 的值的相加,最後回傳 Promise resolve 的值。
// Input:
promise1 = new Promise(resolve => setTimeout(() => resolve(2), 20));
promise2 = new Promise(resolve => setTimeout(() => resolve(5), 60));
// Output:
// 7
解題
前面的寫法:
const addTwoPromises = (promise1, promise2) => {
return Promise.all([promise1, promise2])
.then(([result1, result2]) => {
return result1 + result2;
})
};
addTwoPromises(promise1, promise2)
.then((result) => {
console.log(result); // 7
})
使用 async/await 的寫法:
const addTwoPromises = async (promise1, promise2) => {
const [result1, result2] = await Promise.all([promise1, promise2]);
return result1 + result2;
};
addTwoPromises(promise1, promise2)
.then(console.log); // 7
LeetCode: Sleep
第二個例子會回傳 Promise,題目敘述如下:
Given a positive integer millis, write an asynchronous function that sleeps for millis milliseconds. It can resolve any value.
Example
// Input:
millis = 100
// Output:
// 100
Explanation: It should return a promise that resolves after 100ms.
let t = Date.now();
sleep(100).then(() => {
console.log(Date.now() - t); // 100
});
解題
const sleep = (millis) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(millis);
}, millis)
})
}
let t = Date.now()
sleep(100).then(() => console.log(Date.now() - t)) // 100
let t2 = Date.now();
sleep(200).then(() => {
console.log(Date.now() - t2); // 200
});
setTimeout 更多用法
傳遞參數給回調函數
setTimeout 本身只接受兩個參數,分別是回調函數和延遲時間。
setTimeout:
- 第一個參數是一個回調函數(可以是函數表達式或函數引用),
- 第二個參數是一個時間值,表示多少毫秒後執行該回調函數。
語法如下:
setTimeout(callback, delay);
callback: 要執行的回調函數。 delay: 延遲的時間,以毫秒為單位。 setTimeout 函數不接受其他參數。如果需要向回調函數傳遞參數,可以使用函數包裹的方式,例如:
setTimeout(() => {
// 在這裡執行帶有參數的函數
myFunction(parameter1, parameter2);
}, delay);
這裡使用了箭頭函數或匿名函數,以便在延遲結束後執行 myFunction 並傳遞參數。
定時器 ID
每次成功調用 setTimeout 都會返回一個獨一無二的數字,被稱為「定時器 ID」。這個 ID 可以被用來取消計劃中的定時器,通過傳遞給 clearTimeout 函數。
const timeoutId = setTimeout(() => {
// 這裡是回調函數的內容
}, delay);
// 若要取消計劃中的執行,使用 clearTimeout 並傳入 timeoutId
clearTimeout(timeoutId);
定時器 ID 的使用提供了更多的控制能力,使你能夠在執行前取消或修改計劃中的操作。
setTimeout 範例
LeetCode: Timeout Cancellation
Given a function fn
, an array of arguments args
, and a timeout t
in milliseconds, return a cancel function cancelFn
.
After a delay of cancelTimeMs
, the returned cancel function cancelFn will be invoked.
setTimeout(cancelFn, cancelTimeMs)
Initially, the execution of the function fn
should be delayed by t
milliseconds.
If, before the delay of t
milliseconds, the function cancelFn
is invoked, it should cancel the delayed execution of fn
. Otherwise, if cancelFn
is not invoked within the specified delay t
, fn
should be executed with the provided args
as arguments.
解題
這個問題要求實現一個 cancellable 函式,該函式接收三個參數:一個函式 fn、一個參數陣列 args 和一個延遲時間 t。cancellable 函式返回一個可取消的函式 cancelFn。
- 在函式內部,創建了兩個變數 timeoutId 和 isCancelled,分別用於存儲計時器 ID 和是否已取消的狀態。
- 使用 setTimeout 函式創建一個計時器,該計時器在 t 毫秒後執行一個回調函式。
- 在回調函式內部,檢查 isCancelled 的值。如果尚未取消,則執行給定的函式 fn,並將參數 args 傳遞給它。
- 定義了一個取消函式 cancelFn,該函式用於取消計時器。如果尚未取消,則使用 clearTimeout 取消計時器,並將 isCancelled 設置為 true。
- 返回 cancelFn,以便外部代碼可以在需要時調用它來取消計時器。
const cancellable = (fn, args, t) => {
let timeoutId;
let isCancelled = false;
timeoutId = setTimeout(() => {
if (!isCancelled) {
fn(...args);
}
}, t);
const cancelFn = () => {
if (!isCancelled) {
clearTimeout(timeoutId);
isCancelled = true;
}
};
return cancelFn;
}
// Example usage:
fn = (x) => x * 5
const cancelFunction = cancellable(fn, [2], 20);
// To cancel the execution before the delay:
// cancelFunction();
以上程式如果在取消後還可以重複使用嗎?
是的,這個程式在取消後仍然可以重複使用。一旦計時器被成功取消,isCancelled 變量被設置為 true,並且取消函式 cancelFn 不會再執行任何操作。當下次調用 cancellable 函式時,會創建一個新的計時器和取消函式,使其能夠再次使用。這樣的實作方式確保了取消後的可重複使用性。
LeetCode: Interval Cancellation
Given a function fn
, an array of arguments args
, and an interval time t
, return a cancel function cancelFn
.
After a delay of cancelTimeMs
, the returned cancel function cancelFn
will be invoked.
setTimeout(cancelFn, cancelTimeMs)
The function fn
should be called with args
immediately and then called again every t
milliseconds until cancelFn
is called at cancelTimeMs
ms.
Example 1:
// Input:
fn = (x) => x * 2, args = [4], t = 35
//Output:
[
{"time": 0, "returned": 8},
{"time": 35, "returned": 8},
{"time": 70, "returned": 8},
{"time": 105, "returned": 8},
{"time": 140, "returned": 8},
{"time": 175, "returned": 8}
]
Explanation:
const cancelTimeMs = 190;
const cancelFn = cancellable((x) => x * 2, [4], 35);
setTimeout(cancelFn, cancelTimeMs);
Every 35ms, fn(4) is called. Until t=190ms, then it is cancelled. 1st fn call is at 0ms. fn(4) returns 8. 2nd fn call is at 35ms. fn(4) returns 8. 3rd fn call is at 70ms. fn(4) returns 8. 4th fn call is at 105ms. fn(4) returns 8. 5th fn call is at 140ms. fn(4) returns 8. 6th fn call is at 175ms. fn(4) returns 8. Cancelled at 190ms