使用Fetch

原文連接:css-tricks.com/using-fetch… 。 本文介紹了Fetch基本使用方法及zlFetch庫的使用javascript

不管用JavaScript發送或獲取信息,咱們都會用到Ajax。Ajax不須要刷新頁面就能發送和獲取信息,能使網頁實現異步更新。css

幾年前,初始化Ajax通常使用jQuery的ajax方法:html

$.ajax('some-url', {
  success: (data) => { /* do something with the data */ },
  error: (err) => { /* do something when an error happens */}
});複製代碼

也能夠不用jQuery,但不得不使用XMLHttpRequest,然而這是至關複雜前端

幸好,瀏覽器如今支持Fetch API,能夠無須其餘庫就能實現Ajaxjava

瀏覽器支持

桌面瀏覽器

手機/平板電腦

全部主要的瀏覽器(除了Opera Mini和老的IE)都支持Fetch。針對不支持的,可使用Fetch polyfillnode

Fetch獲取數據

使用Fetch獲取數據很容易。只須要Fetch你想獲取資源。git

假設咱們想經過GitHub獲取一個倉庫,咱們能夠像下面這樣使用:github

fetch('https://api.github.com/users/chriscoyier/repos');複製代碼

Fetch會返回Promise,因此在獲取資源後,可使用.then方法作你想作的。web

fetch('https://api.github.com/users/chriscoyier/repos')
  .then(response => {/* do something */})複製代碼

若是這是你第一次碰見Fetch,也許驚訝於Fetch返回的response。若是console.log返回的response,會獲得下列信息:ajax

{
  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返回的響應能告知請求的狀態。從上面例子看出請求是成功的(oktruestatus是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發送也很簡單,只須要配置三個參數。

fetch('some-url', options);複製代碼

第一個參數是設置請求方法(如postputdel),Fetch會自動設置方法爲get

第二個參數是設置頭部。由於通常使用JSON數據格式,因此設置ContentTypeapplication/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()...複製代碼

Fetch處理異常

雖然但願Ajax響應成功,可是仍會有問題出現:

  1. 可能嘗試獲取不存在的資源
  2. 沒有權限獲取資源
  3. 輸入參數有誤
  4. 服務器拋出異常
  5. 服務器超時
  6. 服務器崩潰
  7. API更改
  8. ...

假設咱們試圖獲取不存在錯誤,並瞭解如何處理錯誤。下面的例子我將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"
}複製代碼

大部分是同樣的,只有okstatusstatusText是不一樣的,正如所料,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異常或rejectPromise來調用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語句中。

可是rejectPromise時,只輸出字符串不太好。這樣不清楚哪裏出錯了,你確定也不會想在異常時,輸出下面這樣:

讓咱們在看看響應:

{
  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中獲取statusstatusText,咱們能夠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狀態很適用。

  • 401: Unauthorized
  • 404: Not found
  • 408: Connection timeout
  • ...

但對於下面這些特定的錯誤不適用:

  • 400:Bad request
    例如,若是請求錯誤缺乏必要的參數,就會返回400.

    光在catch中告訴狀態及緣由短語並不足夠。咱們須要知道缺乏什麼參數。
    因此服務器須要返回一個對象,告訴形成錯誤請求緣由。若是使用Node和Express,會返回像下面這樣的響應:
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

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:

總結

Fetch是很好的方法,能發送和接收數據。不須要在編寫XHR請求或依賴於jQuery。

儘管Fetch很好,可是其錯誤處理不是很直接。在處理以前,須要讓錯誤信息進入到catch方法中。

使用zlFetch庫,就不須要擔憂錯誤處理了。


喜歡此文的同窗,能夠關注個人知乎專欄前端亂燉大雜燴,避免錯過更新

相關文章
相關標籤/搜索