大家好,由于最近比较忙,直到今天我才有时间写第二篇关于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的安装、配置及使用