Fetch漫游指南--篇2

版权声明:转载请声明原地址 https://blog.csdn.net/dk2290/article/details/84978819

大家好,由于最近比较忙,直到今天我才有时间写第二篇关于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节点。 ↩︎

猜你喜欢

转载自blog.csdn.net/dk2290/article/details/84978819