fetch與axios的區(qū)別(fetch的真正用法)

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

無論用JavaScript發(fā)送或獲取信息,我們都會用到Ajax。Ajax不需要刷新頁面就能發(fā)送和獲取信息,能使網(wǎng)頁實(shí)現(xiàn)異步更新。

幾年前,初始化Ajax一般使用jQuery的ajax方法:

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

也可以不用jQuery,但不得不使用XMLHttpRequest,然而這是相當(dāng)復(fù)雜

幸虧,瀏覽器現(xiàn)在支持Fetch API,可以無須其他庫就能實(shí)現(xiàn)Ajax

瀏覽器支持

桌面瀏覽器

[圖片上傳失敗...(image-86b25f-1549421484742)]

手機(jī)/平板電腦

[圖片上傳失敗...(image-2dd331-1549421484742)]

所有主要的瀏覽器(除了Opera Mini和老的IE)都支持Fetch。針對不支持的,可以使用Fetch polyfill

Fetch獲取數(shù)據(jù)

使用Fetch獲取數(shù)據(jù)很容易。只需要Fetch你想獲取資源。

假設(shè)我們想通過GitHub獲取一個倉庫,我們可以像下面這樣使用:

fetch('https://api.github.com/users/chriscoyier/repos');

Fetch會返回Promise,所以在獲取資源后,可以使用.then方法做你想做的。

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

如果這是你第一次遇見Fetch,也許驚訝于Fetch返回的response。如果console.log返回的response,會得到下列信息:

{
  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返回的響應(yīng)能告知請求的狀態(tài)。從上面例子看出請求是成功的(oktrue,status是200),但是我們想獲取的倉庫名卻不在這里。

顯然,我們從GitHub請求的資源都存儲在body中,作為一種可讀的流。所以需要調(diào)用一個恰當(dāng)方法將可讀流轉(zhuǎn)換為我們可以使用的數(shù)據(jù)。

Github返回的響應(yīng)是JSON格式的,所以調(diào)用response.json方法來轉(zhuǎn)換數(shù)據(jù)。

還有其他方法來處理不同類型的響應(yīng)。如果請求一個XML格式文件,則調(diào)用response.text。如果請求圖片,使用response.blob方法。

所有這些方法(response.json等等)返回另一個Promise,所以可以調(diào)用.then方法處理我們轉(zhuǎn)換后的數(shù)據(jù)。

fetch('https://api.github.com/users/chriscoyier/repos')
  .then(response => response.json())
  .then(data => {
    // data就是我們請求的repos
    console.log(data)
  });

可以看出Fetch獲取數(shù)據(jù)方法簡短并且簡單。

接下來,讓我們看看如何使用Fetch發(fā)送數(shù)據(jù)。

Fetch發(fā)送數(shù)據(jù)

使用Fetch發(fā)送也很簡單,只需要配置三個參數(shù)。

fetch('some-url', options);

第一個參數(shù)是設(shè)置請求方法(如postputdel),F(xiàn)etch會自動設(shè)置方法為get

第二個參數(shù)是設(shè)置頭部。因?yàn)橐话闶褂肑SON數(shù)據(jù)格式,所以設(shè)置ContentTypeapplication/json。

第三個參數(shù)是設(shè)置包含JSON內(nèi)容的主體。因?yàn)镴SON內(nèi)容是必須的,所以當(dāng)設(shè)置主體時會調(diào)用JSON.stringify。

實(shí)踐中,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響應(yīng)成功,但是仍會有問題出現(xiàn):

  1. 可能嘗試獲取不存在的資源
  2. 沒有權(quán)限獲取資源
  3. 輸入?yún)?shù)有誤
  4. 服務(wù)器拋出異常
  5. 服務(wù)器超時
  6. 服務(wù)器崩潰
  7. API更改
  8. ...

假設(shè)我們試圖獲取不存在錯誤,并了解如何處理錯誤。下面的例子我將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));

然而卻得到下面這樣結(jié)果:

image

獲取失敗,但是第二個.then方法會執(zhí)行。

如果console.log此次響應(yīng),會看出不同:

  {
  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上沒有發(fā)現(xiàn)chrissycoyier

上面響應(yīng)告訴我們Fetch不會關(guān)心AJAX是否成功,他只關(guān)心從服務(wù)器發(fā)送請求和接收響應(yīng),如果響應(yīng)失敗我們需要拋出異常。

因此,初始的then方法需要被重寫,以至于如果響應(yīng)成功會調(diào)用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異?;?code>rejectPromise來調(diào)用catch。

// throwing an Error
else {
  throw new Error('something went wrong!')
}

// rejecting a Promise
else {
  return Promise.reject('something went wrong!')
}

這里選擇Promise.reject,是因?yàn)槿菀讛U(kuò)展。拋出異常方法也不錯,但是無法擴(kuò)展,唯一益處在于便于棧跟蹤。

所以,到現(xiàn)在代碼應(yīng)該是這樣的:

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));
image

這樣錯誤就會進(jìn)入catch語句中。

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

image

讓我們在看看響應(yīng):

  {
  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狀態(tài)或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狀態(tài)很適用。

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

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

  • 400:Bad request
    例如,如果請求錯誤缺少必要的參數(shù),就會返回400.


    image

    光在catch中告訴狀態(tài)及原因短語并不足夠。我們需要知道缺少什么參數(shù)。
    所以服務(wù)器需要返回一個對象,告訴造成錯誤請求原因。如果使用Node和Express,會返回像下面這樣的響應(yīng):

res.status(400).send({
  err: 'no first name'
})

無法在最初的.then方法中reject,因?yàn)殄e誤對象需要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)
      }
    })
}

首先我們調(diào)用response.json讀取服務(wù)器發(fā)來的JSON數(shù)據(jù),response.json返回Promise,所以可以鏈?zhǔn)秸{(diào)用.then方法。

在第一個.then中調(diào)用第二個.then,因?yàn)槲覀內(nèi)韵Mㄟ^repsonse.ok判斷響應(yīng)是否成功。

如果想發(fā)送狀態(tài)和原因短語,可以使用Object.assign()將二者結(jié)合為一個對象。

let error = Object.assign({}, json, {
  status: response.status,
  statusText: response.statusText
})
return Promise.reject(error)

可以使用這樣新的handleResponse函數(shù),讓數(shù)據(jù)能自動的進(jìn)入.then.catch中。

fetch('some-url')
  .then(handleResponse)
  .then(data => console.log(data))
  .catch(error => console.log(error))

處理其他響應(yīng)類型

到現(xiàn)在,我們只處理JSON格式的響應(yīng),而返回JSON格式數(shù)據(jù)大約占90%。

至于其他的10%呢?

假設(shè)上面的例子返回的是XML格式的響應(yīng),也許會收到下面異常:

image

這是因?yàn)閄ML格式不是JSON格式,我們無法使用response.json,事實(shí)上,我們需要response.text,所以我們需要通過判斷響應(yīng)的頭部來決定內(nèi)容格式:

.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...
  }
});

當(dāng)我遇見這種問題時,我嘗試使用ExpressJWT處理身份驗(yàn)證,我不知道可以發(fā)生JSON響應(yīng)數(shù)據(jù),所以我將XML格式設(shè)為默認(rèn)。

這是我們到現(xiàn)在完整代碼:

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函數(shù),所以可以不用生成此函數(shù),不需要擔(dān)心響應(yīng)來處理數(shù)據(jù)和錯誤。

典型的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還能無須轉(zhuǎn)換成JSON格式就能發(fā)送JSON數(shù)據(jù)。

下面兩個函數(shù)做了同樣事情,zlFetch加入Content-type然后將內(nèi)容轉(zhuǎn)換為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處理身份認(rèn)證也很容易。

常用方法是在頭部加入Authorization,其值設(shè)為Bearer your-token-here。如果你需要增加token選項(xiàng),zlFetch會幫你創(chuàng)建此域。

所以,下面兩種代碼是一樣的:

let token = 'someToken'
zlFetch('some-url', {
  headers: {
    Authorization: `Bearer ${token}`
  }
});

// Authentication with JSON Web Tokens with zlFetch
zlFetch('some-url', {token});

下面就是使用zlFetch來從GitHub上獲取repos:

總結(jié)

Fetch是很好的方法,能發(fā)送和接收數(shù)據(jù)。不需要在編寫XHR請求或依賴于jQuery。

盡管Fetch很好,但是其錯誤處理不是很直接。在處理之前,需要讓錯誤信息進(jìn)入到catch方法中。

使用zlFetch庫,就不需要擔(dān)心錯誤處理了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容