使用Fetch API和ES6生成器來構建異步API
ECMAScript 6 為JavaScript帶來了大量的新特性, 它讓JavaScript能夠更好的構建大型應用。在這些特性中, promise 和 生成器(Generator) 為開發者進行異步編程帶來了極大便利。 另一項對開發者有用的新技術是瀏覽器新增的 Fetch API ,它致力于取代作為當前遠程資源通信基礎的 XMLHttpRequest 。 本文將介紹了是如何結合Fetch API和生成器來構建異步API。
Statement
- 原文地址: http://www.sitepoint.com/asynchronous-apis-using-fetch-api-es6-generators/
- 譯者: 景莊 ,Web開發者,主要關注JavaScript、Node.js、React、Docker等。 </ul>
Fetch API
Fetch API的方法返回的是ES6 Promise 對象,可以與生成器相結合共同完成復雜的異步操作。并且在這種方式下,異步操作的方法可以鏈接起來, 連接后的每一個操作取決于前一次操作中返回的值。對于異步操作而言,你需要重復的向服務器發起請求以獲得最近的更新。
Chrome, Opera, Firefox和Android瀏覽器 的最新版本都支持Fetch API。 對于不支持的瀏覽器,你可以借助于 fetch-polyfill 來提供輔助實現。 另外,為了便于你閱讀與學習,本文中的代碼可以查看 這個Github倉庫 。
在異步操作中使用生成器
如果你對生成器還不了解,可以參考下面幾篇文章:
我們如何使用生成器來執行異步操作呢?如果我們分析生成器的工作方式的話,很快就能得到答案。
生成器函數其實是基于迭代器實現的,并且有如下的結構:
javascript function *myIterator() { while(condition) { yield value; } }
yield 關鍵字負責返回結果,它會暫停迭代器函數的執行直到它被再一次的調用。它也會記住函數的狀態, 而不是在下次執行的時候重新運行一切,它能夠有效的記住上一次暫停的地方。
因此,對于上述代碼你可以不使用循環語句來實現,如下:
```javascript function *myIterator(){ //calculate value 1 yield value1;
//calculate value 2 yield value2; ... //calculate value n yield valuen; } ```
這兩種寫法下,函數的行為其實是等同的。使用 yield 關鍵字唯一原因是它可以在下一次迭代前暫停函數的執行 (在內部它是異步操作)。并且 yield 語句可以返回任何值,你可以通過它返回 Promise ,或者讓函數運行多重異步調用。
在Fetch API中使用生成器
如果你對Fetch API不是很了解的話,你可以參考 這篇文章 。
正如前面所說的,Fetch API被設計用來取代傳統的 XMLHttpRequest 。這個新的API能夠提供對HTTP請求完整的控制權, 并且返回的是一個 Promise 對象,基于服務端返回的響應決定是resolve還是reject。
長輪詢
將Fetch API和生成器組合起來使用的一個場景是 長輪詢 。 長輪詢是一種通過客戶端不斷發送請求給服務器直到獲得響應的技術。生成器可以用于這樣的場景來不斷的yielding響應直到響應包含數據。 長輪詢的過程如下圖所示。
為了來模擬長輪詢的過程,下面會首先實現一個簡單的Express REST API用于響應請求, 每5次請求會返回一次城市的天氣信息。REST API設計如下:
```javascript var polls=0;
app.get(‘/api/currentWeather’, function(request, response){ console.log(polls, polls<5); if(polls < 5){ console.log(“sending…empty”); polls++; response.send({}); } else{ console.log(“sending…object”); response.send({ temperature: 25, sky: “Partly cloudy”, humid: true }); polls = 0; } }); ```
現在讓我們編寫生成器函數來不斷的調用這個API,每次迭代會返回一個 Promise 對象。 對客戶端而言,我們并不知道需要多少次迭代才能從服務器獲得到數據。因此, 下面的方法使用的是無窮循環來不斷的ping服務器。代碼如下:
javascript function *pollForWeatherInfo() { while(true) { yield fetch('/api/currentWeather', { method: 'get' }).then(d => d.json()); } }
我們需要一個函數來不斷的調用這個函數,并且檢查每次返回的Promise是否存在天氣信息。 可以使用一個在下一次迭代時調用的遞歸函數來實現,并且只在發現了從服務器返回的值的時候才暫停這一過程。 下面的代碼展示了上述過程的實現:
```javascript function runPolling(generator){ if(!generator){ generator = pollForWeatherInfo(); }
var p = generator.next(); p.value.then(function(d){ if(!d.temperature){ runPolling(generator); } else { console.log(d); } }); }
runPolling(); ```
正如代碼所述,第一次調用 runPolling() 會創建生成器對象。 next() 方法會返回一個包括 value 屬性的對象, 在這里, value 包括的值是來自于 fetch() 方法返回的 Promise 對象。當promise被通過時, 它要么包括一個空對象(當 polls 值小于5時返回),要么包括對應的天氣信息。
下一步,我們需要檢查對象的 temperature 屬性,如果這個值不存在,我們將 generator 對象傳遞回下一個函數調用 (這樣可以避免丟失生成器的狀態),或者將對象的值打印在控制臺中。
你可以從 倉庫 中下載代碼, 安裝相應的的依賴,啟動服務器,然后瀏覽器 http://localhost:8000 。你應該會從控制臺看到如下的結果:
0 true sending...empty 1 true sending...empty 2 true sending...empty 3 true sending...empty 4 true sending...empty 5 false sending...object
多重依賴的異步調用
很多情況下,我們需要解決的是多重依賴的異步調用,也就是鎖,每個后繼的異步操作取決于前一個異步操作返回的值。 如果我們有一組這樣的操作,并且它們需要被調用多次,我們可以將它們一起放到生成器函數中,在需要的時候執行它。
為了進一步說明這一點,我將會調用 Github API 。 借助這個API我們能夠獲取用戶、組織、或倉庫的基本信息。通過這個API我們還能夠獲取一個倉庫或組織的貢獻者, 然后我們將獲取的數據顯示在屏幕上。
基于此,我們需要調用三個不同的端點。需要執行的任務如下:
- 獲得某個組織的詳細信息
- 如果組織存在的話,獲得組織所擁有的倉庫
- 獲得組織中某個倉庫(隨機選擇)涉及的貢獻者
下面我們來創建一個函數包裹Fetch API,這樣可以避免重復的創建請求頭和構建請求對象:
```javascript function wrapperOnFetch(url){ var headers = new Headers(); headers.append(‘Accept’, ‘application/vnd.github.v3+json’); var request = new Request(url, {headers: headers});
return fetch(request).then(function(res){ return res.json(); }); } ```
下面的函數會調用上面的函數,每次調用都會yields一個 Promise 對象:
```javascript function* gitHubDetails(orgName) { var baseUrl = “https://api.github.com/orgs/”; var url = baseUrl + orgName;
var reposUrl = yield wrapperOnFetch(url); var repoFullName = yield wrapperOnFetch(reposUrl); yield wrapperOnFetch(`https://api.github.com/repos/${repoFullName}/contributors`); } ```
現在,讓我們來寫一段代碼邏輯用來調用上面的函數,進而獲取生成器, 然后使用從服務器返回的值傳遞到UI層中。因為每次調用生成器的 next 方法會返回 promise , 我們將會把這些 promise 鏈接起來。下面的代碼展示了一個簡單的骨架,在代碼中使用了上面函數返回的生成器:
```javascript var generator = gitHubDetails(“aspnet”);
generator.next().value.then(function (userData) { //Update UI
return generator.next(userData.repos_url).value.then(function (reposData) { return reposData; }); }).then(function (reposData) { //Update UI return generator.next(reposData[randomIndex].full_name).value.then(function (selectedRepoCommits) { //Update UI }); }); ```
上面只是一個代碼骨架,詳細的代碼你可以參考 這個倉庫 , 你需要通過npm安裝相應的依賴,并將代碼放在服務器中。
總結
本文詳細介紹了如何在Fetch API結合生成器函數來構建異步API。ECMAScript 6為JavaScript開發者帶來了大量新的特性, 可以幫助開發者編寫更優雅、更健壯的代碼,難道你不應該擁抱ES6,在你的項目中嘗試使用ES6的這些新特性嗎? 趕緊開始嘗試這些新技術吧。
來自: http://wwsun.github.io/posts/async-api-using-fetch-and-generators.html