由於早期 Javascript 設計上的缺陷,一方面使用 Ajax 送出 http 請求時,舊時代的 API 異常繁複,非得使用 JS 框架操作 Ajax 才比較方便,例如 jQuery。
一方面 Ajax 是非同步(Asynchronous)執行緒,會造成前端工作一些麻煩,例如後續事務需要 Ajax 返回資料才能處理時,必須另外拉個 callback 函數執行才不會報錯。但又不能真的把 Ajax 工作變成 "同步" 執行緒,否則網頁的渲染都被 Ajax 請求給塞住,如此就失去 Ajax 非同步處理的意義了。
如果該頁面的工作單純,只有一、兩個 callback 倒也不妨事。但若一個頁面有十幾個、甚至數十個 callback 要執行,那麼這些非同步執行緒的管理就十分頭疼,此時就是所謂「callback 地獄」的感受了。
最近剛好遇到多種複雜的 Ajax callback 需要處理,於是學習一下 HTML5 新的 API,不但讓 Ajax 的操作變得異常簡潔,還能完美解決 callback 地獄的窘境。本篇會整理一些常用情境的範例程式碼,可視需求快速套用。
(圖片出處: pexels.com)
一、經典非同步 callback 金字塔
首先看一下什麼是異步執行的「callback 地獄」,下圖是經典程式碼,取自「asynchronous flow control made right」: 連綿不絕的 callback 函數會形成九十度的金字塔形狀,有時甚至會造成程度判讀上的障礙。這篇「你懂 JavaScript 嗎?Callback」提供了一個不錯的範例,巢狀 callback 太多層時,函數的執行順序是難以判讀的:二、jQuery 非同步排程
如果能夠打破巢狀結構,讓 callback 函數依序執行,就不會讓程式有閱讀與判讀上的困擾了。jQuery 不但是很好的 JS 框架,還提供了非同步執行緒很好的處理方式,也許後來 HTML5 API 處理非同步的部分就是從 jQuery 借鏡的。 以下是 jQuery 執行 Ajax 的語法結構,可參考「jQuery.ajax()」:$.ajax({參數})
.then(callback_A)
.then(callback_B)
.then(callback_C)
這是非常優雅的處理方式,邏輯上也比巢狀結構容易理解:
- 先執行完第一次 Ajax 的 http 請求
- 完成之後,才依序執行 callback_A ~ callback_C 函數
- 而 callback_A ~ callback_C 函數也能放入非同步的 Ajax 程式碼,讓所有非同步的執行緒,可以全部依照順序執行
三、Fetch API 非同步排程
HTML5 增加了 Fetch API 來操作 http 請求,如此就不必另外求助於 JS 框架,可參考官網文件「Using Fetch」。 以下來看看 Fetch 的基本使用方式:fetch("網址", {參數})
.then(callback_A)
.then(callback_B)
.then(callback_C)
使用方式是不是跟 jQuery 一模一樣啊?在 IE 被淘汰後,多數瀏覽器使用 HTML5 已經沒什麼問題,使用瀏覽器原生的 Fetch API 就能擺脫以往的 callback 地獄了。
四、處理未知排程數:Promise
如果一個頁面固定只有少數幾個 http 排程要處理,那麼程式只要重複寫幾個 then() 就解決了。然而如果 http 排程數不固定,不同頁面的 http 排程數也不一樣,但都得等這些 http 取回資料後才能處理特定工作,那就無法使用 then() 了,因為根本不知道要使用幾個 then()。 Javascript ES6 推出了新的 Promise 物件,剛好可以用來處理非同步的問題,可以一次執行所有等待中的 http 請求,以及相關排程的需求,詳細介紹可參考「JavaScript ES6 Promise Object 物件」。 這個討論串「Fetch in fetch in a loop JS」提供了 Promise 的應用方式,可以處理迴圈中的多個 fetch 請求,以下提供範例程式碼,說明如何處理未知排程數的 http 請求:var fetchUrl = ["網址1", "網址2", "網址3"], // 這裡是所有要執行的 http 請求, 當排程數未知時, 可將所有排程網址用函數製作出一個陣列
promises = [],
i;
for (i in fetchUrl) {
promises.push(fetch(fetchUrl[i])); // 將所有要 fetch 的 http 請求,放入 promises 陣列
}
Promise.all(promises) // 利用 Promise.all 先執行所有排程
.then(function() {
// 然後執行後續的動作
});
程式碼作用請見註解文字說明,當有排程數量不固定時,使用函數製作出 fetchUrl 陣列,利用以上 Promise.all 的功能就可先執行所有排程,再執行 then() 後續的處理動作。
五、JSONP 未知排程數
「未知排程數」一個很好的應用方式為,製作 Blogger 平台的 "相關文章" 工具,首先必須取得某篇文章的所有標籤,並撈取所有這些標籤的最新 n 篇文章來做為母體樣本。 要處理 JSON 的非同步多執行緒,我找到的工具為 jQuery 的 getJSON,再搭配 Promise.all 即可,請見以下範例程式碼:var fetchUrl = ["blogger feed 網址1", "blogger feed 網址2", "blogger feed 網址3"], // 這裡是所有要執行的 JSONP http 請求, 當排程數未知時, 可將所有排程網址用函數製作出一個陣列
promises = [],
i;
for (i in fetchUrl) {
// 將所有 JSONP http 請求,放入 promises 陣列
promises.push($.getJSON(fetchUrl[i], function(json) {
getFeed(json); // 處理 feed 資料
}));
}
Promise.all(promises) // 利用 Promise.all 先執行所有排程
.then(function() {
// 顯示所有相關文章
});
// 處理 feed 資料
function getFeed(json) {
// 整理 json 格式的 feed 資料
}
原理跟前個章節大同小異,請見註解說明即可。
六、async await 非同步排程
除了本篇前述的處理非同步排程技巧,從 Javascript ES7 開始,還多了 async、await 可以更好操作函數,來實現非同步執行緒,詳細的說明可參考這篇教學「簡單理解 JavaScript Async 和 Await」。 雖然新技術很好用,但由於 ES7 目前來說瀏覽器支援度有一定的問題,可參考「Can I Use」這個網站的支援性一覽表。前端工程師貿然大量使用的話,可能會有一部份使用者拼命報錯,所以這部分的內容點到為止,可以等 ES7 普及後再來研究。更多 Javascript 相關技巧:
沒有留言:
張貼留言注意事項:
◎ 勾選「通知我」可收到後續回覆的mail!
◎ 請在相關文章留言,與文章無關的主題可至「Blogger 社團」提問。
◎ 請避免使用 Safari 瀏覽器,否則無法登入 Google 帳號留言(只能匿名留言)!
◎ 提問若無法提供足夠的資訊供判斷,可能會被無視。建議先參考這篇「Blogger 提問技巧及注意事項」。
◎ CSS 相關問題非免費諮詢,建議使用「Chrome 開發人員工具」尋找答案。
◎ 手機版相關問題請參考「Blogger 行動版範本的特質」→「三、行動版範本不一定能執行網頁版工具」;或參考「Blogger 行動版範本修改技巧 」,或本站 Blogger 行動版標籤相關文章。
◎ 非官方範本問題、或貴站為商業網站,請參考「Blogger 免費諮詢 + 付費諮詢」
◎ 若是使用官方 RWD 範本,請參考「Blogger 推出全新自適應 RWD 官方範本及佈景主題」→ 不建議對範本進行修改!
◎ 若留言要輸入語法,"<"、">"這兩個符號請用其他符號代替,否則語法會消失!
◎ 為了過濾垃圾留言,所有留言不會即時發佈,請稍待片刻。
◎ 本站「已關閉自刪留言功能」。