Quantcast
Channel: WFU BLOG
Viewing all articles
Browse latest Browse all 784

前端 JS 如何避免 callback 地獄?Fetch API 及 Promie 使用技巧

$
0
0
js-asynchronous-callback-hell-fetch-promise-api.jpg-前端 JS 如何避免 callback 地獄?Fetch API 及 Promie 使用技巧由於早期 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」: js-asynchronous-callback-hell-fetch-promise-api-1.jpg-前端 JS 如何避免 callback 地獄?Fetch API 及 Promie 使用技巧連綿不絕的 callback 函數會形成九十度的金字塔形狀,有時甚至會造成程度判讀上的障礙。這篇「你懂 JavaScript 嗎?Callback」提供了一個不錯的範例,巢狀 callback 太多層時,函數的執行順序是難以判讀的: js-asynchronous-callback-hell-fetch-promise-api-2.jpg-前端 JS 如何避免 callback 地獄?Fetch API 及 Promie 使用技巧

二、jQuery 非同步排程

如果能夠打破巢狀結構,讓 callback 函數依序執行,就不會讓程式有閱讀與判讀上的困擾了。jQuery 不但是很好的 JS 框架,還提供了非同步執行緒很好的處理方式,也許後來 HTML5 API 處理非同步的部分就是從 jQuery 借鏡的。 以下是 jQuery 執行 Ajax 的語法結構,可參考「jQuery.ajax()」: $.ajax({參數}) .then(callbackA) .then(callbackB) .then(callbackC)這是非常優雅的處理方式,邏輯上也比巢狀結構容易理解:
  • 先執行完第一次 Ajax 的 http 請求
  • 完成之後,才依序執行 callbackA ~ callbackC 函數
  • 而 callbackA ~ callbackC 函數也能放入非同步的 Ajax 程式碼,讓所有非同步的執行緒,可以全部依照順序執行
同時 jQuery Ajax 官網文件還有其他用法,非只有 then() 的方式,例如 fail() 用來處理 http 請求失敗時的執行緒,done() 用來處理 http 請求成功時的執行緒,always() 則是不論成功失敗都會執行。

三、Fetch API 非同步排程

HTML5 增加了 Fetch API 來操作 http 請求,如此就不必另外求助於 JS 框架,可參考官網文件「Using Fetch」。 以下來看看 Fetch 的基本使用方式: fetch("網址", {參數}) .then(callbackA) .then(callbackB) .then(callbackC)使用方式是不是跟 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 篇文章來做為母體樣本。 但是此時就會發現 fetch API 並非萬能,因為要撈 Blogger feed 內容必須使用 JSONP 的方式執行,而 fetch 卻只能取得 JSON 格式資料,不支援 JSONP,所以 fetch 將無法製作 Blogger feed 的相關工具。 要處理 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 相關技巧:

Viewing all articles
Browse latest Browse all 784

Trending Articles