大家好,由於最近比較忙,直到今天我纔有時間寫第二篇關於fetch的介紹。今天我將繼續爲大家介紹Fetch API,現在我先大概列出我們接下來會看到的內容:
response
對象;- 自定義
request()
對象; fetch
的異常處理;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的
USVString
1. - 一個
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 moderedirect
: 對重定向處理的模式: 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)時報錯:
- res.json()
- 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]來實現;目前網上大部分兼容思路都差不多,主要實現兩點就可以了:
- 判斷當前瀏覽器是否已經支持原生
fetch
,如果支持就不作處理;如果不支持則用XMLHttpRequest
(即XHR)來代替實現。- 判斷當前瀏覽器是否支持原生
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個知識點需要大家瞭解:
- 什麼是webpack?
- 什麼是babel?
- 如何在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的安裝、配置及使用