轉載文章:使用 Fetchhtml
不管用JavaScript發送或獲取信息,咱們都會用到Ajax。Ajax不須要刷新頁面就能發送和獲取信息,能使網頁實現異步更新。node
幾年前,初始化Ajax通常使用jQuery的ajax
方法:git
$.ajax('some-url', { success: (data) => { /* do something with the data */ }, error: (err) => { /* do something when an error happens */} });
也能夠不用jQuery,但不得不使用XMLHttpRequest
,然而這是至關複雜github
幸好,瀏覽器如今支持Fetch API,能夠無須其餘庫就能實現Ajaxweb
全部主要的瀏覽器(除了Opera Mini和老的IE)都支持Fetch。針對不支持的,可使用Fetch polyfillajax
使用Fetch獲取數據很容易。只須要Fetch你想獲取資源。express
假設咱們想經過GitHub獲取一個倉庫,咱們能夠像下面這樣使用:npm
fetch('https://api.github.com/users/chriscoyier/repos');
Fetch會返回Promise,因此在獲取資源後,可使用.then
方法作你想作的。json
fetch('https://api.github.com/users/chriscoyier/repos') .then(response => {/* do something */})
若是這是你第一次碰見Fetch,也許驚訝於Fetch返回的response
。若是console.log
返回的response
,會獲得下列信息:api
{ body: ReadableStream bodyUsed: false headers: Headers ok : true redirected : false status : 200 statusText : "OK" type : "cors" url : "http://some-website.com/some-url" __proto__ : Response }
能夠看出Fetch返回的響應能告知請求的狀態。從上面例子看出請求是成功的(ok
是true
,status
是200),可是咱們想獲取的倉庫名卻不在這裏。
顯然,咱們從GitHub請求的資源都存儲在body
中,做爲一種可讀的流。因此須要調用一個恰當方法將可讀流轉換爲咱們可使用的數據。
Github返回的響應是JSON格式的,因此調用response.json
方法來轉換數據。
還有其餘方法來處理不一樣類型的響應。若是請求一個XML格式文件,則調用response.text
。若是請求圖片,使用response.blob
方法。
全部這些方法(response.json
等等)返回另外一個Promise,因此能夠調用.then
方法處理咱們轉換後的數據。
fetch('https://api.github.com/users/chriscoyier/repos') .then(response => response.json()) .then(data => { // data就是咱們請求的repos console.log(data) });
能夠看出Fetch獲取數據方法簡短而且簡單。
接下來,讓咱們看看如何使用Fetch發送數據。
使用Fetch發送也很簡單,只須要配置三個參數。
fetch('some-url', options);
第一個參數是設置請求方法(如post
、put
或del
),Fetch會自動設置方法爲get
。
第二個參數是設置頭部。由於通常使用JSON數據格式,因此設置ContentType
爲application/json
。
第三個參數是設置包含JSON內容的主體。由於JSON內容是必須的,因此當設置主體時會調用JSON.stringify
。
實踐中,post
請求會像下面這樣:
let content = {some: 'content'}; // The actual fetch request fetch('some-url', { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(content) }) // .then()...
雖然但願Ajax響應成功,可是仍會有問題出現:
假設咱們試圖獲取不存在錯誤,並瞭解如何處理錯誤。下面的例子我將chriscoyier
拼錯爲chrissycoyier
// 獲取chrissycoyier's repos 而不是 chriscoyier's repos fetch('https://api.github.com/users/chrissycoyier/repos')
爲了處理此錯誤,咱們須要使用catch
方法。
也許咱們會用下面這種方法:
fetch('https://api.github.com/users/chrissycoyier/repos') .then(response => response.json()) .then(data => console.log('data is', data)) .catch(error => console.log('error is', error));
然而卻獲得下面這樣結果:
獲取失敗,可是第二個.then
方法會執行。
若是console.log
這次響應,會看出不一樣:
{ body: ReadableStream bodyUsed: true headers: Headers ok: false // Response is not ok redirected: false status: 404 // HTTP status is 404. statusText: "Not Found" // Request not found type: "cors" url: "https://api.github.com/users/chrissycoyier/repos" }
大部分是同樣的,只有ok
、status
和statusText
是不一樣的,正如所料,GitHub上沒有發現chrissycoyier
。
上面響應告訴咱們Fetch不會關心AJAX是否成功,他只關心從服務器發送請求和接收響應,若是響應失敗咱們須要拋出異常。
所以,初始的then
方法須要被重寫,以致於若是響應成功會調用response.json
。最簡單方法是檢查response
是否爲ok
。
fetch('some-url') .then(response => { if (response.ok) { return response.json() } else { // Find some way to get to execute .catch() } });
一旦咱們知道請求是不成功的,我能夠throw
異常或reject
Promise來調用catch
。
// throwing an Error else { throw new Error('something went wrong!') } // rejecting a Promise else { return Promise.reject('something went wrong!') }
這裏選擇Promise.reject
,是由於容易擴展。拋出異常方法也不錯,可是沒法擴展,惟一益處在於便於棧跟蹤。
因此,到如今代碼應該是這樣的:
fetch('https://api.github.com/users/chrissycoyier/repos') .then(response => { if (response.ok) { return response.json() } else { return Promise.reject('something went wrong!') } }) .then(data => console.log('data is', data)) .catch(error => console.log('error is', error));
這樣錯誤就會進入catch
語句中。
可是reject
Promise時,只輸出字符串不太好。這樣不清楚哪裏出錯了,你確定也不會想在異常時,輸出下面這樣:
讓咱們在看看響應:
{ body: ReadableStream bodyUsed: true headers: Headers ok: false // Response is not ok redirected: false status: 404 // HTTP status is 404. statusText: "Not Found" // Request not found type: "cors" url: "https://api.github.com/users/chrissycoyier/repos" }
在這個例子中,咱們知道資源是不存在。因此咱們能夠返回404
狀態或Not Found
緣由短語,然而咱們就知道如何處理。
爲了在.catch
中獲取status
或statusText
,咱們能夠reject
一個JavaScript對象:
fetch('some-url') .then(response => { if (response.ok) { return response.json() } else { return Promise.reject({ status: response.status, statusText: response.statusText }) } }) .catch(error => { if (error.status === 404) { // do something about 404 } })
上面的錯誤處理方法對於下面這些不須要解釋的HTTP狀態很適用。
但對於下面這些特定的錯誤不適用:
catch
中告訴狀態及緣由短語並不足夠。咱們須要知道缺乏什麼參數。res.status(400).send({ err: 'no first name' })
沒法在最初的.then
方法中reject
,由於錯誤對象須要response.json
來解析。
解決的方法是須要兩個then
方法。這樣能夠首先經過response.json
讀取,而後決定怎麼處理。
fetch('some-error') .then(handleResponse) function handleResponse(response) { return response.json() .then(json => { if (response.ok) { return json } else { return Promise.reject(json) } }) }
首先咱們調用response.json
讀取服務器發來的JSON數據,response.json
返回Promise,因此能夠鏈式調用.then
方法。
在第一個.then
中調用第二個.then
,由於咱們仍但願經過repsonse.ok
判斷響應是否成功。
若是想發送狀態和緣由短語,可使用Object.assign()
將兩者結合爲一個對象。
let error = Object.assign({}, json, { status: response.status, statusText: response.statusText }) return Promise.reject(error)
可使用這樣新的handleResponse
函數,讓數據能自動的進入.then
和.catch
中。
fetch('some-url') .then(handleResponse) .then(data => console.log(data)) .catch(error => console.log(error))
到如今,咱們只處理JSON格式的響應,而返回JSON格式數據大約佔90%。
至於其餘的10%呢?
假設上面的例子返回的是XML格式的響應,也許會收到下面異常:
這是由於XML格式不是JSON格式,咱們沒法使用response.json
,事實上,咱們須要response.text
,因此咱們須要經過判斷響應的頭部來決定內容格式:
.then(response => { let contentType = response.headers.get('content-type') if (contentType.includes('application/json')) { return response.json() // ... } else if (contentType.includes('text/html')) { return response.text() // ... } else { // Handle other responses accordingly... } });
當我碰見這種問題時,我嘗試使用ExpressJWT處理身份驗證,我不知道能夠發生JSON響應數據,因此我將XML格式設爲默認。
這是咱們到如今完整代碼:
fetch('some-url') .then(handleResponse) .then(data => console.log(data)) .then(error => console.log(error)) function handleResponse (response) { let contentType = response.headers.get('content-type') if (contentType.includes('application/json')) { return handleJSONResponse(response) } else if (contentType.includes('text/html')) { return handleTextResponse(response) } else { // Other response types as necessary. I haven't found a need for them yet though. throw new Error(`Sorry, content-type ${contentType} not supported`) } } function handleJSONResponse (response) { return response.json() .then(json => { if (response.ok) { return json } else { return Promise.reject(Object.assign({}, json, { status: response.status, statusText: response.statusText })) } }) } function handleTextResponse (response) { return response.text() .then(text => { if (response.ok) { return json } else { return Promise.reject({ status: response.status, statusText: response.statusText, err: text }) } }) }
zlFetch庫就是上例中handleResponse
函數,因此能夠不用生成此函數,不須要擔憂響應來處理數據和錯誤。
典型的zlfetch像下面這樣:
zlFetch('some-url', options) .then(data => console.log(data)) .catch(error => console.log(error));
使用以前,須要安裝zlFetch
npm install zl-fetch --save
接着,須要引入到你的代碼中,若是你須要polyfill,確保加入zlFetch以前引入它。
// Polyfills (if needed) require('isomorphic-fetch') // or whatwg-fetch or node-fetch if you prefer // ES6 Imports import zlFetch from 'zl-fetch'; // CommonJS Imports const zlFetch = require('zl-fetch');
zlFetch還能無須轉換成JSON格式就能發送JSON數據。
下面兩個函數作了一樣事情,zlFetch加入Content-type
而後將內容轉換爲JSON格式。
let content = {some: 'content'} // Post request with fetch fetch('some-url', { method: 'post', headers: {'Content-Type': 'application/json'} body: JSON.stringify(content) }); // Post request with zlFetch zlFetch('some-url', { method: 'post', body: content });
zlFetch處理身份認證也很容易。
經常使用方法是在頭部加入Authorization
,其值設爲Bearer your-token-here
。若是你須要增長token
選項,zlFetch會幫你建立此域。
因此,下面兩種代碼是同樣的:
let token = 'someToken' zlFetch('some-url', { headers: { Authorization: `Bearer ${token}` } }); // Authentication with JSON Web Tokens with zlFetch zlFetch('some-url', {token});
下面就是使用zlFetch來從GitHub上獲取repos:
Promise.race()方法,寫一個超時的錯誤方法,好比說3000ms以後拋錯。
Fetch是很好的方法,能發送和接收數據。不須要在編寫XHR請求或依賴於jQuery。
儘管Fetch很好,可是其錯誤處理不是很直接。在處理以前,須要讓錯誤信息進入到catch
方法中。
使用zlFetch庫,就不須要擔憂錯誤處理了。