昨天,我們深入探索了 async
關鍵字。我們揭開了它的核心秘密:它是一個「Promise 製造機」,能將任何函式都轉變為一個承諾未來的容器,並為我們創造了一個可以處理非同步操作的特殊「異次元空間」。
今天,我們將學習 async
的最佳拍檔,也是這個異次元空間中唯一的「時間暫停」魔法——await
關鍵字。
如果說 async
只是打開了通往非同步世界的大門,那麼 await
就是我們在這片新世界中,用來馴服時間猛獸、駕馭複雜流程的終極武器。它有一種神奇的魔力,可以讓我們用最符合直覺的「同步思維」,來編寫清晰、優雅的「非同步程式碼」。
今天的目標,是徹底掌握 await
的用法,並學會搭配 try...catch
來建立健壯、無懈可擊的非同步程式碼。學完今天,你就擁有了解析複雜 BLE 探索流程所需的全套技能。
await
的核心功能:等待與解包await
這個詞的字面意思就是「等待」。在 async
函式中,它的作用也正是如此,但它有兩個強大的核心功能:
暫停執行 (Pause Execution)
當 async
函式執行到 await
這一行時,它會暫停下來,但僅僅是這個 async
函式本身暫停,整個網頁的其他部分(如 UI 互動、動畫)依然流暢運行。
它會耐心等待 await
後面的那個 Promise 完成(無論是 fulfilled 還是 rejected)。
解開承諾 (Unwrap the Promise)
這就是 await
最神奇的地方!如果 Promise 成功 (fulfilled),await
會像拆禮物一樣,直接把 Promise 內部包裝的結果值取出來,交給你。
如果 Promise 失敗 (rejected),await
則會像觸發了警報一樣,將這個錯誤拋出。
await
的黃金法則(再次強調):
await
只能在async
函式內部使用。
讓我們模擬一個需要多個連續步驟才能完成的非同步任務:1. 獲取使用者資料 -> 2. 檢查權限 -> 3. 載入對應數據。
// 步驟 1: 模擬獲取使用者,1秒後回傳使用者物件
function fetchUser(userId) {
return new Promise(resolve => {
console.log(`(1) Fetching user ${userId}...`);
setTimeout(() => resolve({ id: userId, name: 'Alice' }), 1000);
});
}
// 步驟 2: 模擬檢查權限,1秒後回傳權限狀態
function checkPermissions(user) {
return new Promise(resolve => {
console.log(`(2) Checking permissions for ${user.name}...`);
setTimeout(() => resolve(true), 1000);
});
}
// 步驟 3: 模擬載入數據,1秒後回傳數據或拋出錯誤
function loadData(hasPermission) {
return new Promise((resolve, reject) => {
console.log('(3) Loading data...');
if (!hasPermission) {
// 如果沒有權限,就讓 Promise 失敗
return reject(new Error('Permission denied!'));
}
setTimeout(() => resolve({ content: 'Secret data...' }), 1000);
});
}
A. 傳統 .then()
鏈寫法
// fetchUser(1)
// .then(user => {
// return checkPermissions(user);
// })
// .then(hasPermission => {
// return loadData(hasPermission);
// })
// .then(data => {
// console.log('Success (then):', data.content);
// })
// .catch(error => {
// console.error('Failed (then):', error.message);
// });
這種寫法還可以,但層級和回呼函式讓它讀起來有點繞。
B. async/await
終極武器寫法
async function main() {
try {
const user = await fetchUser(1);
const hasPermission = await checkPermissions(user);
const data = await loadData(hasPermission);
// 程式碼能走到這裡,代表上面每一步 await 都成功了!
console.log('Success (await):', data.content);
} catch (error) {
// 如果上面任何一個 await 的 Promise 失敗了,
// 程式碼會立刻跳到這裡來!
console.error('Failed (await):', error.message);
}
}
main();
看到差別了嗎?async/await
版本的程式碼,讀起來就跟普通的同步程式碼一模一樣!它的邏輯清晰、從上到下,完全符合我們的思考習慣。這就是它被稱為「終極武器」的原因。
try...catch
:await
必備的安全網你可能已經註意到,在 async/await
的寫法中,try...catch
語法塊是多麼重要。
為什麼它是必備的? 因為當 await
等待的 Promise 變成 rejected
狀態時,await
會將這個拒絕的原因(通常是一個 Error 物件)當作錯誤直接拋出 (throw)。
如果沒有 try...catch
會發生什麼?
async function riskyOperation() {
console.log('Trying something that might fail...');
// 假設這個 Promise 會失敗
const result = await Promise.reject(new Error('Boom!'));
// 因為上面一行拋出了錯誤,這行永遠不會執行
console.log('This will never be logged.');
}
// 執行 riskyOperation();
// 你會在 Console 看到一個紅色的 "Uncaught (in promise) Error: Boom!"
// 這代表錯誤沒有被處理,在真實應用中可能導致程式中斷!
有了 try...catch
才是專業的寫法
async function safeOperation() {
try {
console.log('Trying something that might fail...');
const result = await Promise.reject(new Error('Boom!'));
console.log('This will never be logged.');
} catch (error) {
// 錯誤被成功捕獲!
console.error('Caught the error gracefully:', error.message);
// 我們可以在這裡更新 UI,告訴使用者操作失敗
// statusText.textContent = '操作失敗,請稍後再試。';
}
}
safeOperation(); // 程式會優雅地處理錯誤,而不會崩潰。
鐵律:當你使用
await
來執行一個可能失敗的非同步操作時,永遠用try...catch
將它包裹起來。
今天,我們為 async
函式注入了靈魂,徹底掌握了 await
關鍵字。
async/await
搭配 try...catch
,這就是我們駕馭 Web Bluetooth API 乃至所有現代非同步 API 的終極武器組合。至此,所有關於非同步的理論準備工作已全部完成。我們已經磨好了劍,接下來就是要真正地出鞘了。
明天,我們將正式進入專案的第三階段。我們將學習 Web Bluetooth API 的基本規則與安全限制,並寫下第一行真正與瀏覽器藍牙功能互動的程式碼:navigator.bluetooth.requestDevice()
。
那麼今天的內容就到這邊,感謝你能看到這裡,在這邊祝你早安、午安、晚安,我們明天見。