fetch的使用:拳打axios、脚踢ajax

如果你是一名对未来开发趋势有所了解的开发者,那么你一定知道,前后端分离的趋势。

现在的web体系中包含这么一项技术,称之为异步请求。

与之相对应的就是,模板页面。它是我我眼中比较旧的技术体系,因为其使用的还是通过模板引擎来生产页面,简单来说类似于asp,jsp那一类,无不例外使用了模板引擎,吹牛归吹牛,现在还是很多东西用的模板引擎开发出来的,是当下的主流技术。

在这种开发模式中,前端的网页要是涉及到后端数据的话,那么百分之百会将页面丢到后端开发者,后端开发者将页面修改为模板,以适应对模板开发的要求。这就有几率触发发生前端与后端开发者之间矛盾,造成对整个团队的开发效率下降。当然,如果团队管理者分发任务得当,工作分工合理,标准制作合理,那么问题也不大。但是如果团队管理者不懂技术,对团队内部的分工也不合理,那么前后端开发者发生了矛盾,能不能解决就不好说了。

在未来的技术开发体系中,前端与后端之间的联系渐渐断开,美曰其名:前后端分离。也就是前端的页面不再会交给后端开发人员做二次修改,而是使用js解析引擎中的网络请求api实现对数据的请求。具体表现为:前端开发者通过JavaScript语言,去调用浏览器内置的JavaScript api,实现从浏览器到服务器的数据请求。

前端开发人员编写的网络程序,应该用怎么样的数据格式请求到后端,或者后端开发者应该怎么配合前端发送数据,这需要前后端开发人员进行预先协商,这个协商和前面通过模板的方式协商不同。前后端分离后协商的内容将减小,主要讨论前端与后端怎么交换数据的问题,也就是关键在于后端网路接口该如何定义,前端数据请求有什么特点以及要求,会发送什么样格式的数据。

如果没有前后端分离,那么可能就不仅仅是协商api如何定义的问题了,可能还会涉及到其他问题,造成团队总体开发效率下降。

基于以上趋势,异步请求是解决前后端分离的一项炙手可热的技术。

现在所能直接使用的异步请求有两种一种称之为ajax,另外一种就是本文要介绍的fetch。当然,你可能会说那jQuery还能异步请求呢!其实jQuery只是包装了下ajax。还有vue开发团队开发的另外一个异步请求,那就是axios,这个组件相对于jQuery要更加高级一点,还封装了一层premise,可以实现对异步请求的链式调用,编程效率更高。

而本文所介绍的fetch api是用来替代ajax的下一代异步网络请求,axios的用法跟fetch相似。fetch关键在于他是原生的,可以在js代码中直接调用,而不同于jQuery、axios需要导入组件。

环境声明

  1. 浏览器 Google Chrome 84.0.4147.89 测试无异常
  2. 浏览器 Firefox 78.0.2 测试无异常
  3. 开发工具1 vscode 1.47
  4. 开发工具2 IntelliJ IDEA 2020.1 (Ultimate Edition) Build #IU-201.6668.121
  5. web服务器 Tomcat 8.5.49
  6. java 编译工具 maven 3.6.2
  7. java 版本 1.8.0_231

参考链接

  1. mdn :fetch的构造器
  2. mdn:fetch的使用demo
  3. mdn:Request对象

阅读本文需要具备的基本条件

  1. 熟悉es2015(es6) 语法
  2. 熟悉es2015 premise 对象
  3. 熟悉JavaScript对象JSON
  4. 掌握java语言*
  5. 熟悉java servlet注解*

以上技术列表中,带有*号结尾的是非必须掌握的,因为文中会涉及到前端到后端的完整过程,如果读者朋友不懂java后端,可以忽略这个要求,忽略后,不影响对fetch api的使用

一 、 fetch api 简介

  • 语法:fetch( UrlStr|RequestObj [,initPropertiesObj] )
  • 返回值:一个被Premise对象包装过的Response对象
  • 特点:
    • fetch发送http请求的时候,默认不携带cookie信息,除非在initPropertiesObj中显式声明
    • 因为fetch()方法返回的是一个被Premise对象包装过的Response对象,所以即使Response对象的响应码是400或者500错误码,也算完成了一次http交互,因为返回400或者500也算一次完整http交互,所以不算Premise失败。
    • fetch()方法不接受跨域的cookie

1. 参数1 urlStr|RequestObj

1.1 使用url字符串作为fetch的第一个参数

fetch的第一个参数,大部分博客中使用的url字符串,作为异步请求的请求地址,其第二参数是可选的。如果使用fetch的时候,只传入了url字符串作为第一个参数,在这种请情况下,fetch只是一个简单的get请求。如果你需要将 fetch 改为post请求,那么就必须要将第二个参数对象中添加 method 属性,且 method 属性值等于字符串POST字面值。

1.2 使用Request对象作为fetch的第一个参数

但你如果使用了Request对象作为fetch的第一个参数,那么就不需要传入fetch的第二个参数了。本质上,如果使用Request对象作为参数,fetch充其量就算个异步请求启动器了。

因本文同大多数博客一样,使用的 url 字符串作为 fetch 的第一个参数,所以下文就只贴上Request对象相关文档链接了。因为在这里介绍 Request 对象的话,那么下文将会出现相当多的内容与这里重复。也就是说在下文介绍的 InitPropertiesObj 参数的时候,其属性与 Request 对象基本一致。
相关文档:mdn:Request构造器文档

2. 参数2 initPropertiesObj (可选)

fetch方法的第二个参数:initPropertiesObj是一个对象,这个对象中定义了请求中的请求头,请求体,方法等内容。如果不传入fetch的第二个参数,那么默认就是一个简单的get请求、

以下开始介绍这个对象会被使用属性,被*号标注的表示是我所认为的、不常用的、需要了解的

  1. method : 请求类型(GET,POST,PUT,OPTIONS,HEAD,DELETE,CONNECT,TRACE)
  2. headers : 请求头字典对象或者Header对象
  3. body : 请求体(jsonstr,FormData对象)
  4. mode* : 请求模式(cors,no-cors,same-origin,navigate),是否是跨域请求
  5. credentials* : 发送的请求是否使用cookie,这里单独取出来配置,是因为默认fetch是不携带cookie发送请求的。可取值:{omit,same-origin,include}
  6. cache* : 定义请求的缓存模式。可取值{default, no-store,reload,no-cache,force-cache,only-if-cached}
  7. redirect* : 定义重定向行为处理方式,可取值:{manual,follow,error}

3. fetch返回值Premise以及Response对象

  • fetch函数调用后,返回值是一个Premise对象实例,其内部包含response对象。可以通过调用Premise的then函数来获取其内部的response对象。then函数的参数是必须是一个带参函数,这样then在内部调用这个传入的函数时,就会将premise内部的response传给这个函数,以达到使用response的目的。传入then函数的函数,可以有返回值,也可以没有返回值。如果有返回值,那么then将会把这个返回值包装成premise返回出来。这样,你可以在then后面继续调用新的premise对象。这就是链式编程,可以无限调用新的premise对象的then函数。

  • response对象的相关文档 : mdn:response对象文档链接 , 通过premise对象的then函数获得其内部的response之后,这个response对于我来说,所用到的属性不多,我下面就简单介绍下相关属性:

    • ok:代表这次请求是否成功,这个是否成功的判断依据就在于http状态码在200-299之间的数字
    • status:表示http状态码

    除了以外,我还常用其自带的函数来解析来自后端发来的数据,通常会用两个response对象的函数:

    • json(): 这个函数通过对后端返回的数据按照json字符串解析为JavaScript对象,在第二大节的时候我会使用到。
    • text():这个函数式直接将后端响应的原始数据体返回。在下文中我不会使用到这个函数。

二、 fetch api的使用

1. 后端基本环境以及配置(java)

  • maven主要依赖
     <dependency  
       <groupId>com.alibaba</groupId 
      <artifactId>fastjson</artifactId>
       <version>1.2.68</version>
     </dependency>
     <dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>javax.servlet-api</artifactId>
       <version>4.0.1</version>
     </dependency>
  • Filter代码
     @WebFilter(urlPatterns = "/json_api")
     public class CharsetFilter implements Filter {
         @Override
         public void init(FilterConfig filterConfig) throws ServletException {
             System.out.println("字符过滤器初始化成功");
         }
         @Override
         public void doFilter(ServletRequest servletRequest, 
             ServletResponse servletResponse, 
             FilterChain filterChain) 
                 throws IOException, ServletException 
         {
             servletRequest.setCharacterEncoding("utf-8");
             servletResponse.setCharacterEncoding("utf-8");
             servletResponse.setContentType("application/json");
             filterChain.doFilter(servletRequest, servletResponse);
         }
         @Override
         public void destroy() {
             System.out.println("字符过滤器销毁成功");
         }
     }
  • servlet代码
@MultipartConfig   //这里必须标注,如果不标注,会出现fetch的post请求数据无法接收的情况
@WebServlet(urlPatterns = "/json_api", name = "test_api")
public class FetchJsonTestServlet extends HttpServlet {
    Map respMap = new HashMap();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
        try (PrintWriter writer = resp.getWriter()) {
            System.out.println(
                String.format("get request,query string 'p' was %s", 
                req.getParameter("p"))
            );
            respMap.put("msg", "这是get返回消息");
            writer.write(JSON.toJSONString(respMap));
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
        System.out.println(
        String.format("post request,Parameter 'p' was %s, parameter 's' was %s",
            req.getParameter("p"), 
            req.getParameter("s")));
        System.out.println(
            String.format("upload file name was %s", 
            req.getPart("f") == null ? 
                "null" : req.getPart("f").getSubmittedFileName()));
    }
}

2. fetch的普通get请求实例(文件index.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>fetch测试</title>
</head>
<body>
</body>
<script>
    fetch('http://localhost/2020722/json_api').then(res => {
        return res.json()
    }).then(json => {
        console.log(json)
    })
</script>
</html>

代码解释:
以上代码表明我的使用fetch请求后,通过调用其返回premise对象的then函数,然后用箭头函数传入给then,然后这个箭头函数内部,通过调用response对象的json()函数,这个json()将后端返回的数据体按照json格式,解析为对象并返回了出来。我编写的这个箭头函数,又将这个解析好的对象返回给了then内部,这样then函数将这个解析好的对象再次包装成一个新的premise对象返回出来。我们继续调用这个新Premise对象then函数,将这个新premise内部被解析好的对象传递给我们的箭头函数,最后我在箭头函数内部将这个对象,输出到了浏览器控制台。

通过Firefox访问index.html,控制台输出结果如下
通过浏览器访问网站index.html的控制台输出
通过GoogleChrome访问inex.html结果如下
在这里插入图片描述
以上代码,整个流程看起来非常紧凑,这是因为在实例中使用了箭头函数,是因为这样的代码看起来整洁、简约、高效,但是对于没有搞懂premise对象的和不熟悉es6语法的读者可能不太友好。

以上index.html中的JavaScript代码等同于:

 function resphandler(response) {
	return response.json()
}
function jsonoutput(jsonobj) {
	console.log(jsonobj)
}
let pre1 = fetch('http://localhost/2020722/json_api')
let pre2 = pre1.then(resphandler)
pre2.then(jsonoutput)

那么,通过以上的直观比较,很容易看出来箭头函数以及premise对象简单高效的优势。

3. Fetch在url中携带参数

fetch('http://localhost/2020722/json_api?p=param'),这种写法等于在浏览器地址栏中输入http://localhost/2020722/json_api?p=param,这两种方式,从http请求本质上来看是一样的,但是结果形式不一样。fetch使用的异步,而地址栏请求使用的是同步

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>fetch测试</title>
</head>
<body>

</body>
<script>
	fetch('http://localhost/2020722/json_api?p=param').then(res =>{
		return res.json()
	}).then(json >={
		console.log(json)
	})
</script>
</html>

不论是通过地址栏还是通过fetch异步请求,在后端控制台都会以下输出:

get request,query string 'p' was param

以上能说明通过地址栏和通过fetch,且fetch不额外添加参数的情况下,本质上http请求都是一个类别。

4. fetch模拟表单提交

从这里开始就要涉及到较多的对象了.现index.html主体代码如下:

<head>
	<meta charset="UTF-8">
    <title>fetch测试</title>
</head>
<body>
    <div id="app">
		<h6>通过fetch提交的表单</h6>
		参数p<input type="text" name="p" value="">
		参数s<input type="text" name="s" value="">
		文件f<input type="file" name="f">
		<button onclick="submit()">提交</button>
	</div>
</body>
<script>
	function submit() {
		let data = new FormData()
		data.append('p', document.querySelector('input[name=p]').value)
		data.append('s', document.querySelector('input[name=s]').value)
		data.append('f', 
		    document.querySelectorAll('input[type=file]').item(0).files.item(0))
		fetch('http://localhost/2020722/json_api', {
			method: 'POST',
			body: data,
		}).then(response =>{
			console.log(response.ok)
		})
	}
</script>

代码解释:
代码中主要模拟了一个普通表单,并携带文件的的post请求。以上代码中我传入了fetch的第二个参数,initPropertiesObj,我给这个对象设置了method方法,也就是说这次异步请求,是一个http的post请求,接着我配置了post请求的请求体,body属性。这里使用了FormData对象,这个对象是用于自定义表单的,对于前端开发者来说,FormData对象就是为了异步请求而存在的。FormData对象这里就不再做过多的介绍

请参考以下文档

通过GoogleChrome提交的表单内容截图
在这里插入图片描述后端接收到的表单内容以及文件如下:

post request,Parameter ‘p’ was p, parameter ‘s’ was s
upload file name was titl2.png

5. fetch提交json数据

index.html 内容修改为:

<head>
    <meta charset="UTF-8">
    <title>fetch测试</title>
</head>

<body>
    <div id="app">
        <h6>通过fetch提交的表单</h6>
        参数p<input type="text" name="p" value="">
        参数s<input type="text" name="s" value="">
        <button onclick="submit()">提交</button>
    </div>
</body>
<script>
    function submit() {
        fetch('http://localhost/2020722/json_api', {
            method: 'POST',
            body: JSON.stringify({
                p: document.querySelector('input[name=p]').value,
                s: document.querySelector('input[name=s]').value,
            }),
            headers: {
                'content-type': 'application/json'
            }
        }).then(response => {
            console.log(response.ok)
        })
    }

</script>

后端servlet doPost方法代码修改为:


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException 
    {
        try (BufferedReader reader = req.getReader()) {
            String jsonStr = reader.readLine();
            System.out.println(jsonStr);
            Map map = JSON.parseObject(jsonStr, Map.class);
            System.out.println(map);
        }
    }

使用Google Chrome提交表单截图
在这里插入图片描述

后端输出如下

{“p”:“param”,“s”:“str”}
{p=param, s=str}

三、踩坑告诫

我所踩的坑,都是为了模拟form标签的默认enctype请求,也就是form标签内部的enctype为application/x-www-form-urlencoded值时,所提交的post表单请求。但是没有模拟成功过,我所做过的实验都失败了。通过总结我发现,fetch post提交的表单数据永远是按照content-type类型为multipart/form-data格式提交的,即使在fetch第二参数initPropertiesObj对象中声明content-type类型为普通表单(application/x-www-form-urlencoded)也没有用。

基于以上总结,能解释后端(java)代码中,必须在servlet上标注@MultipartConfig注解,才能接收到fetch的POST请求,否则servlet是无论如何都没法接收到来自前端的请求数据的。

所以基于目前最主流的提交数据格式来看,fetch的使用场景局限于jsonStr提交和使用FormData对象将数据提交到后端。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值