iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
自我挑戰組

JS 加強筆記系列 第 9

Day 09:promise chaining 之比較寫實的例子

  • 分享至 

  • xImage
  •  

promise chaining 大致可以表達成以下的形式,另外昨天還沒有提到的是,如果所有的任務有同樣的錯誤處理,可以把 catch() 接在鏈接的最後:

doSomething()
    .then(function (result) {
        return doSomethingElse(result);
    })
    .then(function (newResult) {
        return doThirdThing(newResult);
    })
    .then(function (finalResult) {
        console.log(`Got the final result: ${finalResult}`);
    })
    .catch(failureCallback);

用箭頭函式表達:

doSomething()
  .then((result) => doSomethingElse(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(failureCallback);

fetch

發送網路請求是前端常常碰到的非同步情境,例如使用 fetch 方法:

let promise = fetch(url);

這個方法會對傳入的 url 請求資源並回傳一個 promise。伺服器回傳 headers 時,這個 promise 會 resolve 一個 response 物件。要讀到完整的回應,可以呼叫幾種 response 的方法,其中一種是 response.json(),它也會回傳一個 promise,resolve 出 JSON 格式的結果。

假設有個取得使用者資料的操作,可以寫成以下:

fetch('/article/promise-chaining/user.json')
    .then(response => response.json()) // 利用 response.json() 將結果解析為 JSON
    .then(user => alert(user.name));

現實中,在取到使用者資料 (user) 後很可能還有後續的工作,例如利用使用者名稱 (user.name),再去

  • 對 github 發出請求來取得使用者頭像 (第 3 行)
  • 解析結果 (第 4 行)
  • 在頁面上顯示頭像 (githubUser.avatar_url) 三秒後移除 (第 5 行 - 最後)
fetch('/article/promise-chaining/user.json')
    .then(response => response.json())
    .then(user => fetch(`https://api.github.com/users/${user.name}`)) // 3
    .then(response => response.json()) // 4
    .then(githubUser => {
        let img = document.createElement('img');
        img.src = githubUser.avatar_url;
        img.className = 'promise-avatar-example';
        document.body.append(img);

        setTimeout(() => img.remove(), 3000);
    });

假設資料都有接到,頁面也都正常,這個 promise 鏈看起來很好...嗎!?以前我大概就是這樣照著工作順序鏈接完就結束,然後也看不太出來潛在問題或能怎麼改。以下是幾個可以思考的方向。

維持 「可鏈接」狀態

第一個點是最後面的 setTimeout,如果想在移除頭像之後再做點別的事情,現在的程式碼不好做,因為又回到一開始的問題,不知道非同步完成的確切時間點。但如果利用 promise 在移除後發出通知 (resolve),就可以正確接續下去:

fetch('/article/promise-chaining/user.json')
    .then(response => response.json())
    .then(user => fetch(`https://api.github.com/users/${user.name}`))
    .then(response => response.json())
    .then(githubUser => new Promise(function(resolve, reject) { // 這裡改成回傳 promise
        let img = document.createElement('img');
        img.src = githubUser.avatar_url;
        img.className = 'promise-avatar-example';
        document.body.append(img);

        setTimeout(() => {
          img.remove();
          resolve(githubUser); // 移除頭像後 resolve
        }, 3000);
    }))
    // 這裡會在三秒後執行
    .then(githubUser => alert(`Finished showing ${githubUser.name}`));

這也就延伸到一個重點原則:碰到非同步任務都應該回傳 promise。即便現在還用不到,回傳 promise 讓程式碼更靈活。

整理程式碼

所有工作比較確定後,或許可以考慮拆分整理程式碼。這件事一開始做有點卡,但實際工作感受是蠻值得做的,因為不同操作或 api 可能看起來彼此獨立,其實有很多共通邏輯,整理之後會比較輕簡、語意清楚。希望之後的文章有機會寫到工作中的整理實例。

// 抽出請求邏輯
function loadJson(url) {
    return fetch(url)
        .then(response => response.json());
}

function loadGithubUser(name) {
    return loadJson(`https://api.github.com/users/${name}`);
}

function showAvatar(githubUser) {
    return new Promise(function(resolve, reject) {
        let img = document.createElement('img');
        img.src = githubUser.avatar_url;
        img.className = 'promise-avatar-example';
        document.body.append(img);

        setTimeout(() => {
          img.remove();
          resolve(githubUser);
        }, 3000);
    });
}

// 實際使用
loadJson('/article/promise-chaining/user.json')
    .then(user => loadGithubUser(user.name))
    .then(showAvatar)
    .then(githubUser => alert(`Finished showing ${githubUser.name}`));
    // ...

上一篇
Day 08: promise chaining
下一篇
Day 10:務必回傳 promise
系列文
JS 加強筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言