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

Posted 山河已无恙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嗯,还在用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包装
npm install node-fetch@2

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

#!/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文件

[
	
		"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

// -*- 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 6Promise对象,在Node环境,都是基于HTTP模块实现,不同点,axios在浏览器中,是基于XMLHttpRequests来实现异步通信的,而fetch是一个新的API,是XMLHttpRequest的最新替代技术 ,下面是一个axios的例子.

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语法改写,使得语义更清晰。

// -*- 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里面,这样才能捕捉异步操作中可能发生的错误.

=====
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 回应

const response = await fetch(url);

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

// -*- 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);
    
)()
[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:属性返回请求的类型。可能的值如下:

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

// -*- 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里面

@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

[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

@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

报错误请求

[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来判断

// -*- 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

@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
[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循环进行遍历。

// -*- 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);
    
)()
[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文件。

@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/>"
// -*- 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(以上是关于嗯,还在用Ajax嘛?Fetch了解一下呀的主要内容,如果未能解决你的问题,请参考以下文章

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

还在用搜索引擎吗?微信搜索了解一下?送给材料党+白嫖党

还在用NuGet吗?大哥FuGet了解一下

还在用Synchronized?Atomic你了解不?

还在用Synchronized?Atomic你了解不?

Pygame实战:这年头塔除了拆还能干什么?这款好玩上瘾的塔防游戏,了解一下嘛