嗯,还在用Ajax嘛?Fetch了解一下呀!

此刻你在沙漠里,因此你要潜心于沙漠之中。沙漠和世上其他东西一样,可以用来理解世界。你甚至不必理解沙漠,只要观察普通的沙粒就行,从中你可以看到天地万物的神奇之处。——–《牧羊少年的人生之旅》

写在前面


此刻你在沙漠里,因此你要潜心于沙漠之中。沙漠和世上其他东西一样,可以用来理解世界。你甚至不必理解沙漠,只要观察普通的沙粒就行,从中你可以看到天地万物的神奇之处。——–《牧羊少年的人生之旅》


Fetch API 提供了一个获取资源的接口(包括跨域请求)。任何使用过XMLHttpRequest的人都能轻松上手,而且新的 API 提供了更强大和灵活的功能集。

Fetch 提供了对RequestResponse,Headers(以及其他与网络请求有关的)对象的通用定义

fetch() 必须接受一个参数——资源的路径。无论请求成功与否,它都返回一个Promise对象,resolve 对应请求的 Response。你也可以传一个可选的第二个参数 init

一旦Response被返回,就可以使用一些方法来定义内容的形式,以及应当如何处理内容,你也可以通过 Request() 和 Response() 的构造函数直接创建请求和响应,但是我们不建议这么做。

Fetch 接口

  • Headers:相当于 response/request 的头信息
  • Request:相当于一个资源请求
  • Response:相当于请求的响应

使用 Fetch

Fetch API 提供了一个JavaScript接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

fetch 规范与jQuery.ajax()主要有以下的不同:

  • 当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的Promise不会被标记为 reject,即使响应的 HTTP 状态码 404 或 500。相反,它会将Promise状态标记为 resolve (如果响应的 HTTP 状态码不在 200 - 299 的范围内,则设置 resolve 返回值的 ok 属性为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject
    +fetch不会发送跨域 cookies,除非你使用了credentials的初始化选项。(自2018 年 8 月以后,默认的 credentials 政策变更为 same-origin。Firefox 也在 61.0b13 版本中进行了修改)
  • fetch()使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁。
  • fetch()采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。
  • fetch()通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。

在用法上,fetch()接受一个URL字符串作为参数,默认向该网址发出GET请求,返回一个 Promise 对象。

环境准备

这里我们用Node环境来学习,当然在浏览器更有可比性,需要安装基于Node的依赖包node-fetch,这里一定要注意版本问题

  • node-fetch用于服务器端,即只能在nodejs中用
  • whatwg-fetch用于客户端,即用于在浏览器没有原生支持fetch的情况
  • isomorphic-fetch可以在nodejs浏览器两种环境中运行,是对whatwg-fetch包装
1
npm install node-fetch@2

同时我们需要一个Web服务用作测试,这里用python搭一个简单的Web服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
# Python 3.9.0
# pip install flask
'''
@File : fetch.py
@Time : 2022/03/04 18:59:02
@Author : Li Ruilong
@Version : 1.0
@Contact : 1224965096@qq.com
@Desc : Fetch学习Demo
'''

from time import sleep
from flask import Flask,jsonify,request,send_from_directory
import os

# configuration
DEBUG = True

app = Flask(__name__)



@app.route("/")
@app.route("/index")
def default():
'''
@Time : 2022/03/04 18:58:42
@Author : Li Ruilong
@Version : 1.0
@Desc : 默认页面
'''
return "<h1>Fetch学习Demo<h1/>"

@app.route('/init', methods=['GET'])
def init():
'''
@Time : 2022/03/04 19:41:40
@Author : Li Ruilong
@Version : 1.0
@Desc : get请求返回JSON
'''

data = ["Ajax","Fetch","Promise","Axios"]
return jsonify(data)

@app.route("/add",methods=["POST"])
def add():
'''
@Time : 2022/03/04 19:43:05
@Author : Li Ruilong
@Version : 1.0
@Desc : Post请求
'''
data = request.json
print(*data, sep='\n')
return jsonify({"msg":"Post请求成功","code":"0"})


@app.route("/download/<filename>")
def download(filename):
print(filename)
'''
@Time : 2022/03/04 22:30:12
@Author : Li Ruilong
@Version : 1.0
@Desc : 下载文件
'''
directory = os.getcwd()
print(directory)
return send_from_directory(directory, filename, as_attachment=True)


@app.route('/upload', methods=['POST', 'PUT'])
def upload():
'''
@Time : 2021/12/15 10:32:03
@Author : Li Ruilong
@Version : 1.0
@Desc : 上传文件
'''
if request.method == 'POST':
try:
f = request.files['file']
print("上传的文件名:===", f.filename)
basepath = os.path.dirname(__file__) # 当前文件所在路径
upload_path = os.path.join(basepath, "\\", str(f.filename))
f.save(upload_path)
print("保存的文件路径:"+upload_path)
except Exception as e:
print("上传文件失败", e)
return jsonify({"msg":"上传文件OK","code":"0"}),200

@app.route("/stop/<int:s>")
def stop(s):
sleep(s)
return "OK",200


if __name__ == '__main__':
app.run(host='127.0.0.1', port=37881, debug=DEBUG)

data.json文件

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"site": "npr",
"link": "http://www.npr.org/rss/rss.php?id=1001",
"type": "rss"
},
{
"site": "npr",
"link": "http://www.npr.org/rss/rss.php?id=1008",
"type": "rss"
}
]

准备工作做好以后,我们开始愉快的学习吧

一个基本的fetch请求设置起来很简单。看看下面的代码:

这是一个回调风格的请求,从服务器获取JSON数据。在Node环境的一个Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/
const fetch = require("node-fetch");

fetch('http://127.0.0.1:37881/download/data.json')
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log('Request Failed', err));

fetch()接收到的response是一个Stream对象,response.json()是一个异步操作,取出所有内容,并将其转为 JSON 对象

整理上看和axios类似,相同点都是基于ES 6 Promise对象,在Node环境,都是基于HTTP模块实现,不同点,axios在浏览器中,是基于XMLHttpRequests来实现异步通信的,而fetch是一个新的API,是XMLHttpRequest的最新替代技术 ,下面是一个axios的例子.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const axios = require('axios').default;
const { v4: uuidv4 } = require('uuid');

let subscriptionKey = "3c6588c7026b41a4**7f81551cb4a737";
let endpoint = "https://api.translator.azure.cn/";


let location = "chinanorth";

axios({
baseURL: endpoint,
url: '/translate',
method: 'post',
headers: {
'Ocp-Apim-Subscription-Key': subscriptionKey,
'Ocp-Apim-Subscription-Region': location,
'Content-type': 'application/json',
'X-ClientTraceId': uuidv4().toString()
},
params: {
'api-version': '3.0',
'from': 'zh-Hans',
'to': ['zh-Hant', 'en']
},
data: [{
'text': '我徒然学会了抗拒热闹,却还来不及透悟真正的冷清。--------张大春'
}],
responseType: 'json'
}).then(function(response){
console.log(JSON.stringify(response.data, null, 4));
}).catch(function (error) {
console.log(error);
});

Promise 可以使用await语法改写,使得语义更清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/


const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/download/data.json';
try {
let response = await fetch(url);
let data =await response.json();
console.log(data);
} catch (e) {
console.log("Oops, error", e);
}
})()

await语句必须放在try...catch里面,这样才能捕捉异步操作中可能发生的错误.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
=====
PS D:\GolandProjects\code-master\demo> node fetch
[
{
site: 'npr',
link: 'http://www.npr.org/rss/rss.php?id=1001',
type: 'rss'
},
{
site: 'npr',
link: 'http://www.npr.org/rss/rss.php?id=1008',
type: 'rss'
}
]
PS D:\GolandProjects\code-master\demo>

Response 对象:处理 HTTP 回应

fetch()请求成功以后,得到的是一个Response对象。它对应服务器的 HTTP 回应

1
const response = await fetch(url);

Response 包含de同步属性,对应 HTTP 回应的标头信息(Headers),可以立即读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/init';
try {
let response = await fetch(url);
//同步属性,对应 HTTP 回应的标头信息(Headers),可以立即读取
console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
console.log(response.type);
console.log(response.url);
console.log(response.redirected)
//Response 包含的数据通过 Stream 接口异步读取
let data =await response.json();
console.log(data);
} catch (e) {
console.log("Oops, error", e);
}
})()
1
2
3
4
5
6
7
8
9
10
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
true
200
OK
undefined
http://127.0.0.1:37881/init
false
[ 'Ajax', 'Fetch', 'Promise', 'Axios' ]

[Done] exited with code=0 in 0.253 seconds

response.ok:属性返回一个布尔值,表示请求是否成功,true对应 HTTP 请求的状态码 200 到 299,false对应其他的状态码。

response.status:属性返回一个数字,表示 HTTP 回应的状态码(例如200,表示成功请求)。

response.statusText:属性返回一个字符串,表示 HTTP 回应的状态信息(例如请求成功以后,服务器返回”OK”)。

response.url:属性返回请求的 URL。如果 URL 存在跳转,该属性返回的是最终 URL。

response.type:属性返回请求的类型。可能的值如下:

通过状态码判断请求是否成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/init';
try {
let response = await fetch(url);
if (response.status >= 200 && response.status < 300){
let data = await response.json();
console.log(data);
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()

我们把python的web服务接口里抛出一个异常,直接到了catch里面

1
2
3
4
5
6
7
8
9
10
11
12
@app.route('/init', methods=['GET'])
def init():
'''
@Time : 2022/03/04 19:41:40
@Author : Li Ruilong
@Version : 1.0
@Desc : get请求返回JSON
'''

data = ["Ajax","Fetch","Promise","Axios"]
raise Exception('这是一个请求异常的模拟')
return jsonify(data)

执行报错:内部服务器错误,即500

1
2
3
4
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
Oops, error Error: INTERNAL SERVER ERROR
at d:\GolandProjects\code-master\demo\fetch.js:23:15
at processTicksAndRejections (internal/process/task_queues.js:93:5)

修改接口返回状态码为400

1
2
3
4
5
6
7
8
9
10
11
@app.route('/init', methods=['GET'])
def init():
'''
@Time : 2022/03/04 19:41:40
@Author : Li Ruilong
@Version : 1.0
@Desc : get请求返回JSON
'''

data = ["Ajax","Fetch","Promise","Axios"]
return jsonify(data),400

报错误请求

1
2
3
4
5
6
7
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
INTERNAL SERVER ERROR
Oops, error Error: INTERNAL SERVER ERROR
at d:\GolandProjects\code-master\demo\fetch.js:24:19
at processTicksAndRejections (internal/process/task_queues.js:93:5)

[Done] exited with code=0 in 0.261 seconds

也可以直接通过response.ok来判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/init';
try {
let response = await fetch(url);
if (response.ok){
let data = await response.json();
console.log(data);
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()

修改接口返回状态码为404

1
2
3
4
5
6
7
8
9
10
11
@app.route('/init', methods=['GET'])
def init():
'''
@Time : 2022/03/04 19:41:40
@Author : Li Ruilong
@Version : 1.0
@Desc : get请求返回JSON
'''

data = ["Ajax","Fetch","Promise","Axios"]
return jsonify(data),404
1
2
3
4
5
6
7
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
NOT FOUND
Oops, error Error: NOT FOUND
at d:\GolandProjects\code-master\demo\fetch.js:24:19
at processTicksAndRejections (internal/process/task_queues.js:93:5)

[Done] exited with code=0 in 0.257 seconds

Response.headers 属性

Response 对象还有一个Response.headers属性,指向一个Headers对象,对应 HTTP 回应的所有标头。

Headers 对象可以使用for...of循环进行遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/init';
try {
let response = await fetch(url);
if (response.ok){
let data = await response.json();
console.log(data);
for (let [key, value] of response.headers) {
//console.log(key+":"+ value);
console.log(`${key} : ${value}`);
}
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()
1
2
3
4
5
6
7
8
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
[ 'Ajax', 'Fetch', 'Promise', 'Axios' ]
content-length : 51
content-type : application/json
date : Sat, 05 Mar 2022 15:14:47 GMT
server : Werkzeug/2.0.2 Python/3.9.0

[Done] exited with code=0 in 0.26 seconds

Headers 对象提供了以下方法,用来操作标头。HTTP 回应来说,修改标头意义不大

  • Headers.get():根据指定的键名,返回键值。
  • Headers.has(): 返回一个布尔值,表示是否包含某个标头。
  • Headers.set():将指定的键名设置为新的键值,如果该键名不存在则会添加。
  • Headers.append():添加标头。
  • Headers.delete():删除标头。
  • Headers.keys():返回一个遍历器,可以依次遍历所有键名。
  • Headers.values():返回一个遍历器,可以依次遍历所有键值。
  • Headers.entries():返回一个遍历器,可以依次遍历所有键值对([key, value])。
  • Headers.forEach():依次遍历标头,每个标头都会执行一次参数函数。

读取内容的方法

Response对象根据服务器返回的不同类型的数据,提供了不同的读取方法。读取方法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据`。

  • response.text():得到文本字符串。
  • response.json():得到 JSON 对象。
  • response.blob():得到二进制 Blob 对象。
  • response.formData():得到 FormData 表单对象。
  • response.arrayBuffer():得到二进制 ArrayBuffer 对象。

response.text()可以用于获取文本数据,比如HTML文件。

1
2
3
4
5
6
7
8
9
10
@app.route("/")
@app.route("/index")
def default():
'''
@Time : 2022/03/04 18:58:42
@Author : Li Ruilong
@Version : 1.0
@Desc : 默认页面
'''
return "<h1>Fetch学习Demo<h1/>"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/';
try {
let response = await fetch(url);
if (response.ok){
let data = await response.text();
console.log(data);
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()

response.json() 主要用于获取服务器返回的 JSON 数据

response.formData()主要用在 Service Worker 里面,拦截用户提交的表单,修改某些数据以后,再提交给服务器。

response.blob()用于获取二进制文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/download/data.json';
try {
let response = await fetch(url);
if (response.ok){
let data = await response.blob();
console.log(data);
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()
1
2
3
4
5
6
7
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
Blob {
[Symbol(type)]: 'application/json',
[Symbol(buffer)]: <Buffer 5b 0a 09 7b 0a 09 09 22 73 69 74 65 22 3a 20 22 6e 70 72 22 2c 0a 09 09 22 6c 69 6e 6b 22 3a 20 22 68 74 74 70 3a 2f 2f 77 77 77 2e 6e 70 72 2e 6f 72 ... 141 more bytes>
}

[Done] exited with code=0 in 0.847 seconds

response.arrayBuffer()主要用于获取流媒体文件。

1
2
3
4
5
6
7
8
9
10
const audioCtx = new window.AudioContext();
const source = audioCtx.createBufferSource();

const response = await fetch('song.ogg');
const buffer = await response.arrayBuffer();

const decodeData = await audioCtx.decodeAudioData(buffer);
source.buffer = buffer;
source.connect(audioCtx.destination);
source.loop = true;

Response.clone()

Stream 对象只能读取一次,读取完就没了。这意味着,前一节的五个读取方法,只能使用一个,否则会报错。

Response 对象提供Response.clone()方法,创建Response对象的副本,实现多次读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/download/data.json';
try {
let response = await fetch(url);
let response1 = response.clone();
if (response.ok){
let data = await response.json();
let data1 = await response1.json()
console.log(data1);
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
[
{
site: 'npr',
link: 'http://www.npr.org/rss/rss.php?id=1001',
type: 'rss'
},
{
site: 'npr',
link: 'http://www.npr.org/rss/rss.php?id=1008',
type: 'rss'
}
]

[Done] exited with code=0 in 0.25 seconds

Response 对象还有一个Response.redirect()方法,用于将 Response 结果重定向到指定的 URL。该方法一般只用在 Service Worker 里面

Response.body 属性

Response.body属性是 Response 对象暴露出的底层接口,返回一个 ReadableStream 对象,供用户操作。

它可以用来分块读取内容,应用之一就是显示下载的进度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
let url = 'http://127.0.0.1:37881/download/data.json';

fetch(url)
.then(response => response.body)
.then(res => res.on('readable', () => {
let chunk;
while (null !== (chunk = res.read())) {
console.log(chunk.toString());
}
}))
.catch(err => console.log(err));
})()

第二个参数init:定制 HTTP 请求

fetch()的第一个参数是 URL,还可以接受第二个参数,作为配置对象,定制发出的HTTP 请求

HTTP 请求的方法、标头、数据体都在这个对象里面设置

Post请求传递JSON

1
2
3
4
5
6
7
8
9
10
11
@app.route("/add",methods=["POST"])
def add():
'''
@Time : 2022/03/04 19:43:05
@Author : Li Ruilong
@Version : 1.0
@Desc : Post请求
'''
data = request.json
print(*data, sep='\n')
return jsonify({"msg":"Post请求成功","code":"0"})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require("node-fetch");

(async () => {
const url = 'http://127.0.0.1:37881/add';
const body = { name: 'John', surname: 'Smith' };
try {
let response = await fetch(url,{
method: 'post',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json;charset=utf-8'}
});
if (response.ok){
const data = await response.json();
console.log(data);
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()

1
2
3
4
5
6
7
8
name
surname
127.0.0.1 - - [06/Mar/2022 02:27:42] "POST /add HTTP/1.1" 200 -
========
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
{ code: '0', msg: 'Post请求成功' }

[Done] exited with code=0 in 0.293 seconds

文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@app.route('/upload', methods=['POST', 'PUT'])
def upload():
'''
@Time : 2021/12/15 10:32:03
@Author : Li Ruilong
@Version : 1.0
@Desc : 上传文件
'''
if request.method == 'POST':
try:
f = request.files['file']
print("上传的文件名:===", f.filename)
basepath = os.path.dirname(__file__) # 当前文件所在路径
upload_path = os.path.join(basepath, "\\", str(f.filename))
f.save(upload_path)
print("保存的文件路径:"+upload_path)
except Exception as e:
print("上传文件失败", e)
return jsonify({"msg":"上传文件OK","code":"0"}),200
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fs = require('fs');
const fetch = require('node-fetch');
const FormData = require('form-data');
let fileStream = fs.readFileSync("./data.json");//读取文件
let formdata = new FormData();
const mimetype = 'text/plain'
formdata.append("file", fileStream, {
filename: "./data.json",//上传的文件名
contentType: mimetype,//文件类型标识
});
(async () => {
const url = 'http://127.0.0.1:37881/upload';
try {
let response = await fetch(url,{
method: 'post',
body: formdata ,
headers: formdata.getHeaders()
});
if (response.ok){
const data = await response.json();
console.log(data);
return data;
}else{
console.log(response.statusText);
throw new Error(response.statusText);
}
} catch (e) {
console.log("Oops, error", e);
}
})()

1
2
3
4
5
6
上传的文件名:=== data.json
保存的文件路径:d:\data.json
127.0.0.1 - - [06/Mar/2022 01:37:51] "POST /upload HTTP/1.1" 200 -
============
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
{ code: '0', msg: '上传文件OK' }

fetch()配置对象的完整 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const response = fetch(url, {
method: "GET",
headers: {
"Content-Type": "text/plain;charset=UTF-8"
},
body: undefined,
referrer: "about:client", //referrer属性用于设定fetch()请求的referer标头。
referrerPolicy: "no-referrer-when-downgrade", //referrerPolicy属性用于设定Referer标头的规则
mode: "cors", // mode属性指定请求的模式
credentials: "same-origin", //credentials属性指定是否发送 Cookie。
cache: "default", //cache属性指定如何处理缓存
redirect: "follow", //redirect属性指定 HTTP 跳转的处理方法
integrity: "", //integrity属性指定一个哈希值,用于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值。
keepalive: false, /// keepalive属性用于页面卸载时,告诉浏览器在后台保持连接,继续发送数据。
signal: undefined //signal属性指定一个 AbortSignal 实例,用于取消fetch()请求
});

取消fetch()请求

fetch()请求发送以后,如果中途想要取消,需要使用AbortController对象。

1
2
3
4
@app.route("/stop/<int:s>")
def stop(s):
sleep(s)
return "OK",200

请求进去睡眠10s,在5s的时候终止请求,新建AbortController实例,然后发送fetch()请求,配置对象的signal属性必须指定接收AbortController实例发送的信号controller.signal

controller.abort()方法用于发出取消信号。这时会触发abort事件,这个事件可以监听,也可以通过controller.signal.aborted属性判断取消信号是否已经发出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// -*- encoding: utf-8 -*-

/*
*@File : fetch.js
*@Time : 2022/03/04 22:04:04
*@Author : Li Ruilong
*@Version : 1.0
*@Contact : 1224965096@qq.com
*@Desc : Fetch学习
*/

const fetch = require('node-fetch');
//npm install abort-controller
const AbortController = globalThis.AbortController || require('abort-controller')

const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, 5000);

(async () => {
const url = 'http://127.0.0.1:37881/stop/10';
try {
const response = await fetch(url, {signal: controller.signal});
const data = await response.text();
console.log(data)
} catch (error) {
console.log('request was aborted',error);
} finally {
clearTimeout(timeout);
}
})()

1
2
[Running] node "d:\GolandProjects\code-master\demo\fetch.js"
request was aborted AbortError: The user aborted a request.

Node 环境

1
2
3
4
5
PS D:\GolandProjects\code-master\demo> node -v
v12.13.1
PS D:\GolandProjects\code-master\demo> npm -v
6.12.1
PS D:\GolandProjects\code-master\demo> npm init -y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "fetch.js",
"dependencies": {
"abort-controller": "^3.0.0",
"form-data": "^4.0.0",
"node-fetch": "^2.6.7"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

python 环境

1
2
3
4
5
6
7
8
9
PS D:\GolandProjects\code-master> python -V
Python 3.9.0
PS D:\GolandProjects\code-master> pip -V
pip 20.2.3 from d:\python\python310\lib\site-packages\pip (python 3.9)
PS E:\docker> flask --version
Python 3.9.0
Flask 2.0.2
Werkzeug 2.0.2
PS E:\docker>
发布于

2022-03-04

更新于

2023-06-21

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×