iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0
Modern Web

Web Bluetooth API 實戰:30 天打造通用 BLE 偵錯工具系列 第 15

Day 15:現代非同步解方 (2):Promise Chaining 串連探索步驟

  • 分享至 

  • xImage
  •  

昨天,我們正面迎擊了 JavaScript 中最核心的挑戰——非同步程式設計。透過「點咖啡」的生動比喻,我們理解了 Promise 如同一張「取餐券」,並學會了使用 async/await 這一現代語法,來優雅地等待我們的「咖啡」準備好。

但是,與藍牙裝置的完整互動,遠比點一杯咖啡要複雜。它更像是一場「尋寶任務」,需要一步一步地解開謎題,每一步都依賴於上一步的結果,而且每一步都需要時間

這個尋寶流程是這樣的:

  1. 首先,你需要在茫茫大海中找到藏寶圖所在的島嶼(掃描 到裝置)。

  2. 然後,你需要登陸島嶼並建立一個營地(連接 到裝置)。

  3. 然後,你需要根據藏寶圖的指示,找到島上那個藏著寶藏的山洞(取得 服務)。

  4. 最後,你才能在山洞裡拿到那個寶箱(取得 特徵)。

這四個步驟環環相扣,而且每一個都是耗時的非同步操作。你不可能在找到島嶼之前就去尋找山洞。這種「一個非同步操作接著下一個非同步操作」的場景,就是我們今天要精通的核心——Promise Chaining (承諾的串連)

今天,我們將深入探索如何將多個 Promise 像鎖鏈一樣串連起來,以完成複雜的連續任務。我們將學習經典的 .then() 鏈式呼叫,並再次見證 async/await 如何將這個複雜的過程,簡化得如詩一般優雅。這是我們在真正動手連接藍牙裝置前的最後一塊、也是最重要的一塊拼圖。


Day 16 (新手詳解版): 現代非同步解方 (2):Promise Chaining 串連探索步驟

學習目標:

  1. 理解依賴性:明白為什麼與 BLE 裝置互動本質上是一系列有依賴關係的非同步步驟。

  2. 學習 .then():掌握使用 .then() 來串連多個 Promise 的經典方法。

  3. 精通 async/await 序列:將鏈式呼叫的邏輯,用 async/await 寫出更清晰、更易維護的程式碼。


1. 經典方法: .then() 的鏈式呼叫

async/await 普及之前,開發者們使用 .then() 來串連 Promise。它的核心規則是:

如果在 .then() 的回呼函式中 return 了一個新的 Promise,那麼下一個 .then() 就會等待這個新的 Promise 完成,並接收其結果。

讓我們用模擬的函式來演示這場「尋寶任務」。

模擬我們的尋寶工具 (假函式)

假設我們有三個函式,每個都會回傳一個會在 1 秒後完成的 Promise:

// 模擬掃描裝置
function fakeScan() {
  console.log('Step 1: Scanning for the island...');
  return new Promise(resolve => {
    setTimeout(() => resolve({ id: 'Island-A', name: 'Treasure Island' }), 1000);
  });
}

// 模擬連接裝置
function fakeConnect(device) {
  console.log(`Step 2: Connecting to ${device.name}...`);
  return new Promise(resolve => {
    setTimeout(() => resolve({ id: 'Server-A', deviceName: device.name }), 1000);
  });
}

// 模擬獲取服務
function fakeGetService(server) {
  console.log(`Step 3: Getting service from server ${server.id}...`);
  return new Promise(resolve => {
    setTimeout(() => resolve({ uuid: '0000180d-...' }), 1000);
  });
}

使用 .then() 串連任務

console.log('--- Starting the treasure hunt with .then() chain ---');

fakeScan()
  .then(device => {
    // 第一個 .then 接收到 fakeScan 的結果 (device 物件)
    console.log(`Success! Found: ${device.name}`);
    // **關鍵:回傳下一個 Promise,將結果傳遞下去**
    return fakeConnect(device);
  })
  .then(server => {
    // 第二個 .then 接收到 fakeConnect 的結果 (server 物件)
    console.log(`Success! Connected to server: ${server.id}`);
    // **再次回傳下一個 Promise**
    return fakeGetService(server);
  })
  .then(service => {
    // 第三個 .then 接收到 fakeGetService 的結果 (service 物件)
    console.log(`Success! Got service with UUID: ${service.uuid}`);
    console.log('--- Treasure hunt complete! ---');
  })
  .catch(error => {
    // **優點:鏈中的任何一個環節出錯,都會直接跳到這個 catch**
    console.error('Oh no! The hunt failed:', error);
  });

打開開發者工具的 Console 執行這段程式碼,你會看到每隔一秒,就會有一條成功的日誌印出。.then() 鏈就像一節節的火車車廂,順利地將上一步的結果,載送到下一步。

這種寫法雖然可行,但當步驟一多,一層層的 .then() 會讓程式碼看起來像一個「>」符號,可讀性會變差。


2. 現代優雅: async/await 的同步化寫法

現在,讓我們見證奇蹟。我們將使用 async/await 來完成完全相同的任務。

使用 async/await 串連任務

// 我們必須把所有 await 操作都放在一個 async 函式中
async function startTreasureHunt() {
  console.log('--- Starting the treasure hunt with async/await ---');
  try {
    // Step 1: 等待掃描完成,並將結果存入 device 變數
    const device = await fakeScan();
    console.log(`Success! Found: ${device.name}`);

    // Step 2: 使用上一步的 device,等待連接完成
    const server = await fakeConnect(device);
    console.log(`Success! Connected to server: ${server.id}`);

    // Step 3: 使用上一步的 server,等待獲取服務完成
    const service = await fakeGetService(server);
    console.log(`Success! Got service with UUID: ${service.uuid}`);
    
    console.log('--- Treasure hunt complete! ---');

  } catch (error) {
    // 同樣地,任何一個 await 失敗,都會直接跳到 catch
    console.error('Oh no! The hunt failed:', error);
  }
}

// 執行我們的 async 函式
startTreasureHunt();

驚人的對比!

請比較上面兩段程式碼。你會發現:

  • 可讀性async/await 版本的程式碼,讀起來就像普通的同步程式碼,完全符合人類的直覺思考順序:做完第一步,再做第二步...

  • 變數管理:在 async/await 中,device, server, service 這些變數都在同一個 try 區塊中,可以自由使用。但在 .then() 鏈中,每個變數都只活在自己的那個小小的回呼函式裡。

  • 錯誤處理try...catch 的語法是 JavaScript 中處理錯誤的標準結構,比 .catch() 更為通用和直觀。

async/await 並沒有發明新東西,它只是 Promise 的一層「語法糖」,讓我們能用更優雅、更簡單的方式來編寫和閱讀非同步的串連邏輯。這正是我們將在專案中使用的風格


總結與後續

今天,我們補上了非同步程式設計的最後一塊理論拼圖
至此,我們已經對非同步操作的「是什麼」(Promise)、「如何處理」(.then, async/await) 以及「如何串連」(Chaining) 有了全面的認識。
我們將利用接下來的兩天,放慢腳步,將 asyncawait 這兩個威力強大的語法徹底內化。
明天我們將會重新聚焦並深入探討 async 關鍵字本身。我們將會拋開複雜的藍牙連線場景,透過更多元、更基礎的範例,來徹底理解一個函式一旦被標記為 async 之後,它到底發生了什麼本質上的變化?它回傳的到底是什麼?以及我們該如何使用它。

那麼今天的內容就到這邊,感謝你能看到這裡,在這邊祝你早安、午安、晚安,我們明天見。


上一篇
Day 14:非同步的挑戰:理解探索未知裝置的複雜性
系列文
Web Bluetooth API 實戰:30 天打造通用 BLE 偵錯工具15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言