
一、經典非同步 callback 金字塔
首先看一下什麼是異步執行的「callback 地獄」,下圖是經典程式碼,取自「asynchronous flow control made right」:

二、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 官方範本及佈景主題」→ 不建議對範本進行修改!
◎ 若留言要輸入語法,"<"、">"這兩個符號請用其他符號代替,否則語法會消失!
◎ 為了過濾垃圾留言,所有留言不會即時發佈,請稍待片刻。
◎ 本站「已關閉自刪留言功能」。