Fetch漫遊指南--篇2

大家好,由於最近比較忙,直到今天我纔有時間寫第二篇關於fetch的介紹。今天我將繼續爲大家介紹Fetch API,現在我先大概列出我們接下來會看到的內容:

  1. response對象;
  2. 自定義request()對象;
  3. fetch的異常處理;
  4. fetch的兼容實現方案;

一.Response對象

Fetch API 的Response接口呈現了對一次請求的響應數據,Response實例是在fetch()處理完promises之後返回的。

正如上述MDN所說,Response實例是在fetch處理完promise後才返回的,所以我們在一般想拿到數據都會做類似以下處理(我以處理json數據爲例):

fetch(URL)
        .then(res => res.json()) 
        .then(res => { console.log("data", res)})
        .catch(err => { console.log("error", err)})

通常情況下,我們會使用到response對象的以下屬性:

  • Response.status — 整數(默認值爲200) 爲response的狀態碼.
  • Response.statusText — 字符串(默認值爲"OK"),該值與HTTP狀態碼消息對應.
  • Response.ok — 如上所示,該屬性是來檢查response的狀態是否在200-299(包括200,299)這個範圍內.該屬性返回一個Boolean值.

記住Response.ok這個參數,後面在fetch的異常處理中我們會用到它。


二.自定義Request()對象

除了傳給 fetch() 一個資源的地址,你還可以通過使用 Request() 構造函數來創建一個 request 對象,然後再作爲參數傳給fetch()。

語法如下:
var myRequest = new Request(input, init);

參數解釋:
1.input:定義你想要fetch的資源。可以是下面兩者之一:

  • 一個直接包含你希望fetch的資源的URL的 USVString1.
  • 一個 Request 對象.

2.init(可選)

  • method: 請求的方法,例如:GET, POST。
  • headers: 任何你想加到請求中的頭,其被放在Headers對象或內部值爲ByteString 的對象字面量中。
  • body: 任何你想加到請求中的body,可以是Blob, BufferSource, FormData, URLSearchParams, 或 USVString對象。注意GET 和 HEAD請求沒有body。
  • mode: 請求的模式, 比如 cors, no-cors, same-origin, 或 navigate。默認值應該爲 cors。但在Chrome中,Chrome47之前的版本默認值爲no-cors,自Chrome47起,默認值爲same-origin。
  • credentials: 想要在請求中使用的credentials::omit,same-origin,或include。默認值應該爲omit。但在Chrome中,Chrome 47 之前的版本默認值爲 same-origin ,自Chrome 47起,默認值爲include。
  • cache: 請求中想要使用的cache mode
  • redirect: 對重定向處理的模式: follow, error, or manual。在Chrome中,Chrome 47 之前的版本默認值爲 manual ,自Chrome 47起,默認值爲follow。
  • referrer: 一個指定了no-referrer,client,或一個URL的USVString。默認值是client.
  • integrity: 包括請求的subresourceintegrity值(eg:sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=).

3.實例

var myHeaders = new Headers();

var myInit = { method: 'GET',
               headers: myHeaders,
               mode: 'cors',
               cache: 'default' };

var myRequest = new Request('flowers.jpg', myInit);

fetch(myRequest).then(function(response) {
  return response.blob();
}).then(function(myBlob) {
  var objectURL = URL.createObjectURL(myBlob);
  myImage.src = objectURL;
});

上面的實例比較簡單易懂,需要注意的是這個地方,cache: 'default'2

三.Fetch的異常捕捉

自XHR起,封裝一個詳盡的錯誤捕捉機制一直都是個令人頭疼的事兒,當然,早就有人把這個頭疼的事給做了,古代有jquery ajax,現代也有類似axios這之類拋出promise的庫。對fetch來說,它是一個足夠底層的原生模塊,足夠底層,也就意味着更大的自由,以及更繁瑣的封裝過程。

我們一般情況下是這麼創建一個fetch請求的(我以最普遍的json數據流爲例):

fetch('/json', {
        method: 'get'
    })
    .then(res => {
       console.log('data', res);
    })
   .catch(err => {
        console.log('error', err)
    })

如果請求正常通過,那麼不必多說,此時在瀏覽器的控制檯上就會打印出"data": {...}這個json數據的信息。如果我們把上面的url改一下,變成/json1111,此時再去請求,肯定是404,這毋庸置疑,但是有趣的是我們再去瀏覽器的控制檯上查看,此時並有打印出我們想要的error,而是繼續走的"data": {...},我們看下圖:

so~從上圖我們可以看到,似乎可以藉助response中的response.ok == true來判斷請求是否成功,如果條件沒有命中,那就拋出異常內容。

聽起來這個思路沒毛病的樣子,但是在講這個前,我還有一個發現的有趣的東西想和大家分享一下,那就是並不是所有未經封裝的fetch請求失敗時都不會走catch去捕捉error的,我們來看這個例子:

fetch('/json11111', {
        method: 'get'
    })
    .then(res => res.json())
    .then(res => {
       console.log('data', res);
    })
   .catch(err => {
        console.log('error', err)
    })

跟上面的例子比起來,它只多了一步.then(res => res.json()),不理解的可以去翻我的上篇博文。但是大家可以看看它的結果如圖:

有同學可能會問了,誒這是爲什麼?之前都走data的,現在怎麼又走error了?
爲什麼?因爲這個錯誤不是一般的錯誤,人家是錯二代。

SyntaxError 對象代表嘗試解析語法上不合法的代碼的錯誤。-
當Javascript語言解析代碼時,Javascript引擎發現了不符合語法規範的tokens或token順序時拋出SyntaxError.

看到這裏,是不是煥然大悟?你服務器給我返回的數據不符合規範,我res.json()處理不了了,就給你拋了個syserr。有的同學悟性比較好,馬上他就會說,誒,那是不是fetch body裏的5個方法用上去(詳情見我的第一篇博客),請求失敗時都能走err?

遺憾的告訴這位同學,他的想法是錯誤的,body的5個處理數據的方法只有以下兩個方法會在請求失敗(404)時報錯:

  1. res.json()
  2. res.formData()

但是,要注意,這並不是嚴格意義上的結論,實際上什麼方法會走data,什麼會走err,是由你的錯誤請求返回的內容來決定的,通常來說是會由瀏覽器返回給你一個錯誤信息,但是也不排除項目中有攔截修改的可能,所以大家還是要具體情況具體分析。

好,言歸正傳,我們繼續談談怎麼捕捉fetch的錯誤。上面我們已經講了大概的思路,現在我們來實現它:

function errorHandle(res) {
    return res.json()
       .then(json => {
            if (res.ok) {
                 return json;
             } else {
                  return Promise.reject(json)
             }
       })
    }

fetch('error', {
        method: 'get'
    })
    .then(errorHandle)
    .then(res => {
         console.log('data', res);
      })
    .catch(err => {
         console.log('error', err)
      })

大概的封裝就是這樣,這種方式的錯誤信息是由後臺決定的,更自由,更詳細。

四.Fetch的兼容方案

Fetch想要兼容低版本的瀏覽器,只能藉助polyfill3]來實現;目前網上大部分兼容思路都差不多,主要實現兩點就可以了:

  1. 判斷當前瀏覽器是否已經支持原生fetch,如果支持就不作處理;如果不支持則用XMLHttpRequest(即XHR)來代替實現。
  2. 判斷當前瀏覽器是否支持原生Promise,如果支持就不處理;如果不支持則用內部實現的Promise來代替。

這種實現思路可以自己寫替代的polyfill,也可以直接import現成的解決方案,下面我給大家提供一種選擇方案(以下方案全部實際檢驗過可行,時間截止到2018-12-11,由於可能會遇到webpack和babel的更新,所以我會在配置中寫明所需依賴的版本**,大家要注意這一點,因爲很多情況下依賴與依賴之間也是有版本的前置要求的**)。

whatwg-fetch + promise-polyfill/es6-promise方案
顧名思義,whatwg-fetch是fetch的polyfill,promise-polyfill或是es6-promise是Promise的polyfill,這幾個包大家都可以在npm官網中找到。

下面我開始講一個完整的可以在瀏覽器中跑起來的搭建過程,首先有以下3個知識點需要大家瞭解:

  1. 什麼是webpack?
  2. 什麼是babel?
  3. 如何在webpack中使用babel?

由於webpack已經更新到了4.x,babel也更新到了V7,所以其實有很多內容和配置跟以前不一樣了,以後有時間的話我會專門講講這一塊的入門配置搭配以及常見的坑,現在這塊不是我們的主要內容,我會在下面的內容中簡單解釋各種術語,儘量讓大家在能正常跑起來代碼的前提下搞懂這些配置是什麼意思。

第一步,我們進入到我們的項目目錄(以我的目錄爲例):
$ cd E:\webpack

第二步,初始化package.json文件:
$ cnpm init -y
注意:如果和我一樣使用cnpm來代替npm,需要先替換淘寶鏡像:
$ npm install -g cnpm --registry=https://registry.npm.taobao.org

初始化後,我們可以看到一個package.json文件:
在這裏插入圖片描述
第三步,我們需要建立兩個文件夾,一個爲/src,裏面放一個index.js文件,另一個爲/dist,裏面放一個index.html文件,最後在根目錄下建立app.js。如圖目錄結構:
在這裏插入圖片描述

index.js中我們寫入如下代碼:

import 'whatwg-fetch';
import 'promise-polyfill/src/polyfill';

fetch('/get2')
.then(function(response) {
    return response.json()
  }).then(function(json) {
    console.log('parsed json', json)
  }).catch(function(ex) {
    console.log('parsing failed', ex)
  })

index.html中我們寫入如下代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>fetch兼容性測試</h1>
    <script src="/static/js/main.js"></script>
</body>
</html>

之所以要在html中引入main.js,是因爲如果不指定的話,webpack打包的默認出口就是dist/main.js。此文件在打包時會被創建。

app.js中我們寫入如下代碼(node.js提供服務):

const express = require('express');
const static = require('express-static');
const path = require('path');

var app = express();
app.use('/static', static(path.join(__dirname, 'public')));
app.set('view engine','ejs');
app.set('views', path.join(__dirname, 'views'));   

app.get('/', (req, res) => {
    res.render('index.ejs')
})

app.get('/get', (req, res) => {
    res.send({name:"waw",age:12})
})

var server = app.listen('8088', () => {
    var port = server.address().port;
    console.log("run success in port: "+ port)
})

第四步,安裝webpack和webpack-cli
$ cnpm install -g webpack webpack-cli
當然你也可以選擇局部安裝:
$ cnpm install -D webpack webpack-cli
但是當你局部安裝後,進入項目目錄中打開git bash輸入$ webpack -v後會報以下錯誤:

$ webpack -v
bash: webpack: command not found

如果你是局部安裝的webpack以及webpack-cli,你需要這樣來運行webpack命令:
node_modules/.bin/webpack -v
如果是windows平臺,需要用反斜槓:
node_modules\.bin\webpack -v

至於到底選擇哪種方式,本文不作深究,我會另外寫一篇文章專門介紹這塊的內容。

安裝完之後可以通過輸入指令$ webpack -v來確認你是否安裝成功。

第五步,安裝babel-loader, @babel/core和@babel/preset-env
想通過webpack來編譯其他語言(ES6相對之前的javascript語言來說也是其他語言,因爲有很多它解釋不了的語法),必須安裝loader,babel-loader@babel/core@babel/preset-env就是在webpack中搭載babel,來編譯ES6語法的javascript文件。

大家可能在網上看到過這麼安裝的:
$ cnpm install -D babel-loader babel-core babel-preset-env 4

很遺憾的告訴大家,截止到我寫此文章的今天(2018-12-12),如果你是這麼安裝的,那麼你npm run build時是肯定會報錯的。原因嘛,那就是我之前說的,這三個包之間還有版本的前置依賴,也就是說相互有個版本的對應關係。

在npm官網中,找到babel-loader包的主頁,有這麼一段話:

webpack 4.x | babel-loader 8.x | babel 7.x

npm install -D babel-loader @babel/core @babel/preset-env webpack

webpack 4.x | babel-loader 7.x | babel 6.x

npm install -D babel-loader@7 babel-core babel-preset-env webpack

我簡單解釋一下,官方這麼搭配意味着你想加載6.x的babel-core,就必須用7.x的babel-loader;或者,你想加載7.x的babel-core,你就得用8.x的babel-loader;

ok,回到正文,我們正式來安裝一下這三個包:
$ cnpm install -D babel-loader @babel/core @babel/preset-env

第六步,創建webpack.config.js文件以及更新package.json文件
此項步驟主要目的是在webpack的配置中設置loader;我們在項目的根目錄下創建webpack.config.js文件,此時我們的目錄結構如下:
在這裏插入圖片描述

webpack.config.js中寫入以下配置:

module.exports = {
    module:{
        rules:[
            {
                test:/.\js$/,
                exclude:/node_modules/,
                loader:"babel-loader"
            }
        ]
    }
}

然後打開package.json文件,添加一個babel屬性:

"babel": {
    "presets": [
      "@babel/preset-env"
    ]
  }

scripts中添加一條指令:

"build": "webpack --mode production"  //生產模式 必須指定

當然,如果你想使用.babelrc配置也是可以的。同樣的在項目根目錄下創建.babelrc文件,然後寫入以下配置:

{
  "presets": ["env"]
}

此時,我們完整的package.json文件內容如下:

{
  "name": "webpack-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.2.0",
    "@babel/preset-env": "^7.2.0",
    "babel-loader": "^8.0.4",
    "webpack": "^4.27.1",
    "webpack-cli": "^3.1.2"
  },
  "babel": {
    "presets": [
      "@babel/preset-env"
    ]
  },
  "dependencies": {
    "whatwg-fetch": "^3.0.0"
  }
}

第七步,安裝 whatwg-fetch 和 promise-polyfill/es6-promise
$ cnpm install whatwg-fetch promise-polyfill es6-promise

到這,我們所有的準備工作就全部完成了,此時,我們完整的目錄結構如下:
在這裏插入圖片描述

此時,我們在git-bash中輸入$ cnpm run build,出現下圖的話我們就成功了:

在這裏插入圖片描述
然後,我們將dist/main.js的內容,複製到上圖目錄下/public/js/main.js中,ctrl+s,然後conde run app.js啓動服務,打開ie輸入localhost:8088,我們可以看到有正常的請求發出,你也可以打開仿真,選擇ie9,仍然可以看到有請求發出。這就證明我們的兼容方案已經生效了。

五.參考文章/文獻

1.babel-loader
2.es6-promise
3.webpack4.x下babel的安裝、配置及使用


  1. USVString 對應於所有可能的 unicode 標量值序列的集合。 ↩︎

  2. default表示瀏覽器從HTTP緩存中尋找匹配的請求。 ↩︎

  3. Polyfill可以理解爲用低版本環境能解釋的語言實現的新功能的代碼補丁。 ↩︎

  4. -D表示 --save-dev,其含義爲npm install時會將模塊依賴寫入package.json文件中的devDependencies節點。 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章