本系列教程目录Vue3+Element Plus全套学习笔记-目录大纲


第5章 Promise与异步函数async/await的使用

5.1 vue-resource

vue-resource是Vue.js的插件提供了使用XMLHttpRequest或JSONP进行Web请求和处理响应的服务。 曾经是 Vue 社区广泛使用的库之一,用于发送 AJAX 请求和处理响应。当vue更新到2.0之后,作者就宣告不再对vue-resource更新,并且 Vue 官方推荐使用其他更现代的库来处理 HTTP 请求,如 Axios 或者原生的 Fetch API等。

5.2 Promise

Promise 是 JavaScript 中的一种编程模式,用于处理异步操作。它提供了一种更加优雅的方式来组织异步代码,避免了回调地狱(callback hell)的问题,并且使得错误处理更加一致。Promise 对象代表了一个最终会在未来完成(或失败)的异步操作,所以在 Promise 返回给调用者的时候,操作往往还没有完成,并且其结果值未知。

5.2.1 回调地狱

  • 回调函数:当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。我们熟悉的定时器和Ajax中就存在有回调函数:
 //function(){console.log('执行了回调函数')}就是回调函数,它只有在3秒后才会执行
setTimeout(function(){  
    console.log('执行了回调函数');
},3000)  //3000毫秒
  • 异步任务:与之相对应的概念是“同步任务”,同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。同样,还拿定时器作为异步任务举例:
setTimeout(function(){
    console.log('执行了回调函数');
},3000)
console.log('111');

上述代码的实际输出顺序为:“111”、“执行了回调函数”。

  • 回调地狱:

根据前面我们可以得出一个结论:存在异步任务的代码,不能保证能按照顺序执行,那如果我们非要代码顺序执行呢?比如我需要先输出“First”,再输出“Second”,再输出“Third”。我必须要这样操作,才能保证顺序正确:

setTimeout(function () {
    console.log("First");
    setTimeout(function () {
        console.log("Second");
        setTimeout(function () {
            console.log("Third");
        }, 2000);
    }, 2000);
}, 2000);

可以看到,代码中的回调函数套回调函数,居然套了3层,这种回调函数中嵌套回调函数的情况就叫做回调地狱。

实际上,回调地狱的场景还是挺多见的,我们经常需要先发送一段请求查询到某些数据以当做参数传递给下一个请求,这样下一个请求就必须等上一个请求完毕后才能执行。

5.2.2 Promise的使用

Promise 是异步编程的一种解决方案,主要是用来解决回调地狱的问题。

Promise 构造函数接受一个函数作为参数,我们称该函数为起始函数。Promise创建后起始函数会立即执行,并且是同步执行,这意味着后面的代码必须要等待起始函数的任务执行完毕后才会执行。

示例代码:

// 使用while循环,模拟延迟
function sleep(delay) {
    let start = new Date().getTime();
    while (new Date().getTime() - start < delay) {
        continue;
    }
}

// 创建一个 Promise 对象
const promise = new Promise(function (resolve, reject) {
    console.log('Promise start...');
    // 模拟延迟
    sleep(2000)
    console.log('Promise end...');
});

console.log('程序执行完毕');

程序执行效果:

起始函数包含两个函数 resolve 和 reject,分别表示 Promise 成功和失败的状态。在起始函数执行成功时,它应该调用 resolve 函数并传递成功的结果。当起始函数执行失败时,它应该调用 reject 函数并传递失败的原因。

Promise 对象一旦 Promise 被创建,它的状态会在“待定”(pending)、“兑现”(fulfilled)和“拒绝”(rejected)之间转变。

  • 待定状态(pedding):调用promise时(创建Promise对象时),一开始就呈现出等待状态,遇到resolve或者reject之前,都处于这个状态。
  • 兑现状态(fulfilled):在执行了resolve后,promise则会从待定状态变成兑现状态,后续会进入.then 的回调函数中。
  • 拒绝状态(rejected)在执行了reject后,promise状态会从待定状态变成拒绝状态,后续会进入.catch 的回调函数中。

等到Promise任务执行完毕后我们需要调用Promise对象的then方法或者catch方法来获取Promise执行的结果。

示例代码:

// 创建一个 Promise 对象
const promise = new Promise(function(resolve, reject) {

    // 进行异步操作
    setTimeout(() => {
        // 随机生成成功或失败
        if (Math.random() < 0.5) {
            /*
            执行resolve方法对于Promise对象来说是成功
            最终执行Promise对象的then方法,传递的参数也会被传递到then方法中
            */
            console.log('准备执行resolve');
            resolve('success');
        } else {
            /*
            执行reject方法对于Promise对象来说是失败
            最终执行Promise对象的catch方法,传递的参数也会被传递到catch方法
            */
            console.log('准备执行reject');
            reject('error');
        }
    }, 1000);
});

// 注册成功和失败的回调函数(获取Promise对象的状态,但是是异步执行,并不阻塞下面的代码执行)
promise.then(function (result){
    // result的值为resolve方法传入的值
    console.log(result);
}).catch(function (error){
    // error的值为reject方法传入的值
    console.log(error);
}).finally(function (){
    // 无论成功或失败都会执行
    console.log('Promise对象执行完毕');
})

console.log('程序执行完毕');      // 因为Promise对象是异步的,所以先输出

执行效果:

需要注意的是,Promise的起始函数是同步执行的,但then/catch 方法是异步执行的。

示例代码:

// 创建一个 Promise 对象
const promise = new Promise(function (resolve, reject) {
    console.log('Promise start...');
    // 将来(Promise起始函数执行完毕后)会执行then方法里面的函数
    resolve('成功');
    // 模拟延迟
    sleep(2000)
    console.log('Promise end...');
});

promise.then(function (value) {
    // 模拟延迟
    sleep(2000)
    console.log('Promise resolved:', value);
}).catch(function (reason) {
    console.log('Promise rejected:', reason);
}).finally(function () {
    console.log('Promise finally...');
});

console.log('程序执行完毕');

执行效果:

有了Promise,接下来我们可以改造一下回调地狱中的代码了:

function print(delay, message) {
    // 该方法返回的是一个Promise对象
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve();
        }, delay);
    });
}

/*
 1. print(2000, "First").then()开始执行Promise对象里面的方法
    2s后打印"First",之后执行resolve方法,然后将执行then()方法
 2. 在print(2000, "First").then()方法中调用了print(4000, "Second");
    该方法返回一个Promise对象(返回给了print(2000, "First").then()方法)
    4s后打印"Second",之后执行resolve方法,然后将执行then()方法
 3. print(4000, "Second")的then()方法实际上就是print(3000, "Third");然后3s后打印"Third"
 */
print(2000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});

console.log("End..");           // 该代码最先执行

上述代码等价于:

let promise_first = print(2000, "First");

let promise_second = promise_first.then(function () {
    return print(4000, "Second");
});

let promise_third = promise_second.then(function () {
    print(3000, "Third");
});

执行效果:

5.2.3 Promise改造Ajax请求

Promise可以封装一个异步操作,不阻塞当前任务的执行,利用这个特点我们可以用来封装ajax请求,示例代码如下:

const promise = new Promise((resolve, reject) => {
// 执行异步操作(该方法在创建Promise对象时就已经执行)
if (/* 异步操作是否成功 */) {
      resolve(value);// 调用 resolve,代表 Promise 将返回成功的结果(其实就是调用then方法)
    } else {
      reject(error);// 调用 reject,代表 Promise 会返回失败结果(其实就是调用catch方法)
    }
});

示例:

let getRequest = function (url) {
    // 使用Promise对象封装一个ajax请求
    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            type: "GET",
            success(result) {
                // 调用Promise的resolve方法
                resolve(result);
            },
            error(error) {
                // 调用Promise的reject方法
                reject(error);
            }
        });
    })
}

// 模拟一个请求地址(较为耗时)
var url = "https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json";

/*
 执行getRequest(url)方法,Promise起始函数开始执行
 调用then方法,获取Promise成功的结果(需要等待Promise执行完毕),并打印到控制台
 调用catch方法,获取Promise失败的错误(需要等待Promise执行完毕),并打印到控制台
 */
getRequest(url).then(function (result){
    console.log(result);
}).catch(function (error){
    console.log(error);
})

console.log("程序执行完毕");          // 这句代码最先执行

5.2 异步函数-async/await

Promise 的编程模型依然充斥着大量的 then方法,虽然解决了回调地狱的问题,但是在语义方面依然存在缺陷,代码中充斥着大量的 then 函数,这就是 async/await 出现的原因。async/await 让代码更少,更简洁。

5.2.1 async

async 关键字修饰的方法被称为异步方法,其本质是将方法执行的内容包装为一个Promise对象。也就是说,async函数的返回值是一个Promise对象,具体的使用方法和特点我们还需要往下看。

异步函数的核心特点:

  1. 非阻塞执行:异步操作不会阻塞主线程
  2. 语法简洁:避免了 Promise 的链式调用,代码更易读
  3. 错误处理:可以使用传统的 try/catch 结构处理错误

异步函数的语法:

async function functionName() {
  // 函数体
}

或使用箭头函数:

const funcName = async () => {
  // 函数体
}
1)async函数的使用

async 函数的返回值为一个Promise对象,我们观察下列代码:

// async函数的返回值是一个Promise对象
async function timeout() {
    return 'hello world!'
}

console.log(timeout())
console.log('end...')

观察结果:

当async函数正常执行完毕,那么内部会调用Promise.resolve()返回Promise对象,后续将会执行该Promise的then回调函数,并把当前的async函数的返回值传递给resolve()方法。但如果async函数内部抛出错误,那么就会调用Promise.reject()来返回Promise对象,该错误对象会被传递给reject()方法。

我们观察下列代码:

// async函数的返回值是一个Promise对象
async function timeout() {
    // 如果方法没有出现异常, 则返回值会被作为resolve函数的参数
    // 如果方法出现异常, 该异常对象会被作为reject函数的参数
    // throw new Error('出现了异常');
    return 'hello world!'
}

// timeout()方法的返回值为Promise对象
timeout().then(function (result) {
    console.log("then方法:", result)
}).catch(function (error) {
    console.log("catch方法:", error)
})

console.log('end...')

观察结果:

总结下来,其实async函数就是返回了一个Promise对象,当然内部会根据执行代码的不同来选择调用reslove还是reject方法来创建Promise对象,了解到本质后,我们就能理解,其实下面两个写法是等价的:

// fun1
function fun1() {
    return Promise.resolve('TEST');
}

// fun2
async function fun2() {
    return 'TEST';
}
2)async函数异步在哪

Promise的特点是起始函数是同步执行,then/catch 方法是异步执行。因而在async函数中,并非是async函数的执行是异步的,相反它是同步执行的。我们观察下面案例:

// 模拟阻塞
function sleep(delay) {
    let start = new Date().getTime();
    while (new Date().getTime() - start < delay) {
        continue;
    }
}

/*
 async函数是同步执行的, 但是它可以返回一个Promise对象
 async函数执行完毕后, 才会执行then方法里面的代码(then方法是异步的)
 */
async function timeout() {
    // 模拟阻塞
    sleep(2000)
    console.log('timeout函数执行中...')          // 先输出这里(因为async函数是同步执行的)
    return 'hello world!'
}

// 调用timeout函数
timeout()

console.log('end...')               // 再输出这里

执行效果:

可以看到,单纯的使用async函数并非能达到异步效果,而是要结合then方法来达到异步的功能。观察下列代码:

// 模拟阻塞
function sleep(delay) {
    let start = new Date().getTime();
    while (new Date().getTime() - start < delay) {
        continue;
    }
}

/*
 async函数是同步执行的, 但是它可以返回一个Promise对象
 async函数执行完毕后, 才会执行then方法里面的代码(then方法是异步的)
 */
async function timeout() {
    // 模拟阻塞
    sleep(2000)
    console.log('timeout函数执行中...')          // 先输出这里(因为async函数是同步执行的)
    return 'hello world!'
}

// 调用timeout函数
timeout().then(function (result) {
    // 模拟阻塞
    sleep(2000)
    console.log("then:", result)               // 最后输出这里(异步执行)
})

console.log('end...')               			// 再输出这里

执行效果:

5.2.2 await

1)await的使用

对于传统的async函数在调用时只能返回Promise对象,而我们想要获取该Promise对象产生的结果则需要调用该Promise对象的then方法(或catch方法)来接收结果集。

await 是一个只能在async函数内部使用的运算符,加在Promise对象前面,使它会阻塞当前 async 函数的执行,等待 Promise 的完成:

  • 如果 Promise fulfilled(成功),直接返回Promise Resolve方法的值,即then回调函数的参数。
  • 如果 Promise rejected(失败),直接抛出拒绝原因(rejection reason)

Tips:await不仅仅用于等Promise对象,实际是可以接普通函数调用或者直接量的。

观察案例:

// 定义一个函数,模拟请求
async function fetchData() {
    console.log('fetchData...');
    return 'ok';
}

async function startFetchData(){
    // 返回的是Promise对象,可以继续调用then方法获取结果('ok')
    let promise =  fetchData()
    console.log("result:",promise)
}

// 调用startFetchData函数
startFetchData();

console.log("end")

执行效果如下:

但有时我们必须获取当前Promise的结果集才能够执行后续的代码,此时就可以在Promise对象(async函数)前使用await关键字,改为如下代码:

// 定义一个函数,模拟请求
async function fetchData() {
    console.log('fetchData...');
    return 'ok';
}

async function startFetchData() {
    /*
     等待fetchData函数的返回结果,返回值为'ok'(将来要传递给then回调函数的值)
     await会阻塞当前函数的代码,而不阻塞函数外的代码
     */
    let result = await fetchData()
    console.log("result:", result)
}

// 调用startFetchData函数
startFetchData();

console.log("end")

代码:

/*
 等待fetchData函数的返回结果,返回值为'ok'(将来要传递给then回调函数的值)
 await会阻塞当前函数的代码,而不阻塞函数外的代码
 */
let result = await fetchData()
console.log('result:', result);

等价于:

fetchData().then(function(result) {
    console.log('result:', result);
})

执行效果如下:

Tips:await只会阻塞当前函数的代码,不会阻塞函数外的代码,。

2)await与async解决回调地狱

我们观察如下案例:

setTimeout(function () {
    console.log("First");
    setTimeout(function () {
        console.log("Second");
        setTimeout(function () {
            console.log("Third");
        }, 2000);
    }, 2000);
}, 2000);

使用Promise改造:

// 该方法返回的是Promise对象
function print(delay, message) {
    // 该方法返回的是一个Promise对象
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve(message + "ok");
        }, delay);
    });
}

print(2000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});

使用await和async改造:

// 改造后(async/await)
async function getData() {
    // 获取第一个Promise的结果
    const data1 = await print(2000, "First");
    // 获取第二个Promise的结果(等到第一个Promise执行完毕)
    const data2 = await print(2000, "Second");
    // 获取第三个Promise的结果(等到第二个Promise执行完毕)
    const data3 = await print(2000, "Third");
    console.log('data1', data1);
    console.log('data2', data2);
    console.log('data3', data3);
}

// 调用改造后的getData方法
getData();

5.3 axios

5.3.1 axios基本用法

axios 是一个基于 promise 的 HTTP 客户端,用于浏览器和 node.js。它提供了一种现代化的方式来进行 HTTP 请求和接收响应,支持诸如拦截请求和响应、转换请求和响应数据、取消请求等功能。axios 的设计使其易于使用,并且它已经成为许多前端和后端开发者的首选库之一。

axios的语法如表所示。

语法 说明
axios(config) 使用axios发送ajax请求
axios(url[, config]) 使用axios发送ajax请求
axios.request(config) 使用axios发送ajax请求
axios.get(url[, config]) 发送get请求
axios.delete(url[, config]) 发送delete请求
axios.head(url[, config]) 发送head请求
axios.options(url[, config]) 发送options请求
axios.post(url[, data[, config]]) 发送post请求
axios.put(url[, data[, config]]) 发送put请求
axios.patch(url[, data[, config]]) 发送patch请求
axios.postForm(url[, data[, config]]) 使用 multipart/form-data 类型发起 post 请求
axios.putForm(url[, data[, config]]) 使用 multipart/form-data 类型发起 put 请求
axios.patchForm(url[, data[, config]]) 使用 multipart/form-data 类型发起 patch 请求
  • 示例代码:

使用axois发送一个普通请求:

// 发起一个 GET 请求 (默认请求方式)
axios('/user/12345');

// 发起一个 GET 请求 (默认请求方式)
axios('/user/12345');

// 发起一个 GET 请求 (默认请求方式)
axios('/user/12345');

执行GET请求:

// 向给定ID的用户发起请求
axios.get('/user?ID=12345')
  .then(function (response) {
    // 处理成功情况
    console.log(response);
  })
  .catch(function (error) {
    // 处理错误情况
    console.log(error);
  })
  .finally(function () {
    // 总是会执行
  });

携带参数:

// 上述请求也可以按以下方式完成(可选)
axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  .finally(function () {
    // 总是会执行
  });  

axios的响应数据如下:

{
  // `data` 由服务器提供的响应
  data: {},

  // `status` 来自服务器响应的 HTTP 状态码
  status: 200,
  
  // `statusText` 来自服务器响应的 HTTP 状态信息
  statusText: 'OK',

  // `headers` 是服务器响应头
  // 所有的 header 名称都是小写,而且可以使用方括号语法访问
  // 例如: `response.headers['content-type']`
  headers: {},

  // `config` 是 `axios` 请求的配置信息
  config: {},

  // `request` 是生成此响应的请求
  // 在node.js中它是最后一个ClientRequest实例 (in redirects),
  // 在浏览器中则是 XMLHttpRequest 实例
  request: {}
}

5.3.2 axios使用案例

1)pom.xml:

<parent>
    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>2.0.1.RELEASE</version>

</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>

        <artifactId>lombok</artifactId>

    </dependency>

</dependencies>

2)entity:

package com.sd.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Book {
    private Integer id;
    private String name;
    private String author;
}

3)Controller:

package com.sd.controller;

import com.sd.entity.Book;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@RestController
@RequestMapping("/book")
public class BookController {
    @PostMapping("/upload")
    public Map save(MultipartFile pic, HttpServletRequest request) throws Exception{
        String realPath = "D:/000";

        // 前端上传的文件名称
        String fileName = pic.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        String newFileName = System.currentTimeMillis() + suffix;
        pic.transferTo(new File(realPath + "/" + newFileName));
        String url = "http://localhost:8080/upload/" + newFileName;

        return new HashMap(){{
            put("flag",true);
            put("message","图片上传成功");
            put("statusCode","200");
            put("picUrl",url);
        }};
    }

    @PostMapping
    public Map save(@RequestBody Book book){
        return new HashMap(){{
            put("flag",true);
            put("message","新增成功");
            put("book", book);
            put("statusCode","200");
        }};
    }

    @DeleteMapping("{id}")
    public Map delete(@PathVariable Integer id){

        return new HashMap(){{
            put("flag",true);
            put("message","删除成功");
            put("id", id);
            put("statusCode","200");
        }};
    }

    @PutMapping("{id}")
    public Map update(@PathVariable Integer id,@RequestBody Book book){

        return new HashMap(){{
            put("flag",true);
            put("message","修改成功");
            put("id", id);
            put("book", book);
            put("statusCode","200");
        }};
    }

    @GetMapping
    public Map findAll(){
        int i=1/0;
        return new HashMap(){{
            put("flag",true);
            put("message","查询成功");
            put("BookList",
                    Arrays.asList(
                            new Book(1,"《大学》","曾子"),
                            new Book(2,"《庄子》","庄子"),
                            new Book(2,"《道德经》","老子")
                    )
            );
            put("statusCode","200");
        }};
    }
}

demo01.html:

<body>
<div id="box">
    <button @click="save">save</button>

    <hr>
    <button @click="deleteById('10')">deleteById</button>

    <hr>
    <button @click="update('100')">update</button>

    <hr>
    <button @click="findAll">findAll</button>

    <hr>
</div>

<script>

    new Vue({
        el: "#box",
        data: {},
        methods: {
            save: function () { // save
                axios({
                    url: "/book",
                    method: "post",
                    data: {
                        id: 1,
                        name: "《论语》",
                        author: "孔子"
                    }
                }).then(function (response) {
                    console.log(response);
                })
            },
            deleteById: function (id) { // deleteById
                axios("/book/"+id,{
                    method: "delete"
                }).then(function (response) {
                    console.log(response);
                })
            },
            update: function (id) { // update
                axios.request({
                    url: "/book/"+id,
                    method: "put",
                    data: {
                        id: 2,
                        name: "《孟子》",
                        author: "孟子"
                    }
                }).then(function (response) {
                    console.log(response);
                })
            },
            findAll: function () { // findAll
                // 默认发送GET请求
                axios("/book").then(function (response) {
                    console.log(response);
                })
            }
        }
    })
</script>

</body>

demo02.html

<body>
<div id="box">
    <button @click="save">save</button>

    <hr>
    <button @click="deleteById('10')">deleteById</button>

    <hr>
    <button @click="update('100')">update</button>

    <hr>
    <button @click="findAll">findAll</button>

    <hr>
</div>

<script>
    new Vue({
        el: "#box",
        data: {},
        methods: {
            save() {
                axios.post("/book", {id: 10, name: "《永乐大典》", author: "解缙"}).then(function (res) {
                    // data是服务端响应的数据
                    console.log(res.data)
                }).catch(function (err) {
                    console.log(err);
                });
            },
            deleteById(id) {
                axios.delete("/book/" + id).then(function (res) {
                    console.log(res.data)
                }).catch(function (err) {
                    console.log(err);
                });
            },
            update(id) {
                axios.put("/book/" + id, {id: 10, name: "《天工开物》", author: "宋应星"}).then(function (res) {
                    console.log(res.data)
                }).catch(function (err) {
                    console.log(err);
                });
            },
            findAll() {
                axios.get("/book").then(function (res) {
                    console.log(res.data)
                }).catch(function (err) {
                    console.log(err);           // 异常信息
                });
            }
        }
    })
</script>

</body>

demo03.html

<body>
<div id="box">
    <input type="file" id="file">
    <button @click="upload">上传文件</button>

</div>

<script>
    new Vue({
        el: "#box",
        data: {},
        methods: {
            upload() {
                // 构建一个multipart/form-data的请求,用于上传文件
                axios.postForm("http://localhost:8080/book/upload", 
                    // 上传的文件对象,需要用FormData包装一下
                    {
                        pic: document.getElementById("file").files[0]
                    }
                ).then(function (response) {
                    console.log(response);
                }).catch(function (error) {
                    console.log(error);
                });
            }
        }
    })
</script>

</body>

5.4 axios配置

我们可以指定axios的默认配置,这些配置将作用于每个axios请求。配置如下:

// 创建一个axios实例
const instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

//可用的配置项如下:
{
  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认值

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
  // 你可以修改请求头。
  transformRequest: [function (data, headers) {
    // 对发送的 data 进行任意转换处理

    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对接收的 data 进行任意转换处理

    return data;
  }],

  // 自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是与请求一起发送的 URL 参数
  // 必须是一个简单对象或 URLSearchParams 对象
  params: {
    ID: 12345
  },

  // `paramsSerializer`是可选方法,主要用于序列化`params`
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function (params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求体被发送的数据
  // 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
  // 在没有设置 `transformRequest` 时,则必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属: FormData, File, Blob
  // - Node 专属: Stream, Buffer
  data: {
    firstName: 'Fred'
  },
  
  // 发送请求体数据的可选语法
  // 请求方式 post
  // 只有 value 会被发送,key 则不会
  data: 'Country=Brasil&City=Belo Horizonte',

  // `timeout` 指定请求超时的毫秒数。
  // 如果请求时间超过 `timeout` 的值,则请求会被中断
  timeout: 1000, // 默认值是 `0` (永不超时)

  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default

  // `adapter` 允许自定义处理请求,这使测试更加容易。
  // 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
  adapter: function (config) {
    /* ... */
  },

  // `auth` HTTP Basic Auth
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // `responseType` 表示浏览器将要响应的数据类型
  // 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
  // 浏览器专属:'blob'
  responseType: 'json', // 默认值

  // `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
  // 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // 默认值

  // `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
  xsrfCookieName: 'XSRF-TOKEN', // 默认值

  // `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值

  // `onUploadProgress` 允许为上传处理进度事件
  // 浏览器专属
  onUploadProgress: function (progressEvent) {
    // 处理原生进度事件
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  // 浏览器专属
  onDownloadProgress: function (progressEvent) {
    // 处理原生进度事件
  },

  // `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
  maxContentLength: 2000,

  // `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
  maxBodyLength: 2000,

  // `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
  // 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
  // 则promise 将会 resolved,否则是 rejected。
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认值
  },

  // `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
  // 如果设置为0,则不会进行重定向
  maxRedirects: 5, // 默认值

  // `socketPath` 定义了在node.js中使用的UNIX套接字。
  // e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
  // 只能指定 `socketPath` 或 `proxy` 。
  // 若都指定,这使用 `socketPath` 。
  socketPath: null, // default

  // `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
  // and https requests, respectively, in node.js. This allows options to be added like
  // `keepAlive` that are not enabled by default.
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // `proxy` 定义了代理服务器的主机名,端口和协议。
  // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
  // 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
  // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
  // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
  // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
  proxy: {
    protocol: 'https',
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // see https://axios-http.com/zh/docs/cancellation
  cancelToken: new CancelToken(function (cancel) {
  }),

  // `decompress` indicates whether or not the response body should be decompressed 
  // automatically. If set to `true` will also remove the 'content-encoding' header 
  // from the responses objects of all decompressed responses
  // - Node only (XHR cannot turn off decompression)
  decompress: true // 默认值

}

配置axios参数:

// 创建一个axios实例,设置该实例的配置
const instance = axios.create({
    // 请求地址
    baseURL: 'http://localhost:8080/',
    // 请求超时时间
    timeout: 1000,
    // 自定义请求头
    headers: {'X-Custom-Header-Username': 'xiaohui',"X-Custom-Header-Password":"admin"}
});

// 设置全局的axios默认配置
axios.defaults.baseURL = 'http://localhost:8080/';
axios.defaults.headers['X-Custom-Header-Username'] = "xiaolan";
axios.defaults.headers['X-Custom-Header-Password'] = "123456";

5.5 axios拦截器

axios 拦截器允许在请求被发送之前或响应被处理之后执行某些操作。拦截器可以用来添加全局的行为,例如在每个请求中自动加入认证信息、处理错误响应、重试失败的请求等。通过使用拦截器,可以将一些通用的操作抽象出来,从而避免在每个请求中重复相同的代码。

请求拦截器:

/**
 * 添加请求拦截器
 * config: 请求的配置对象
 */
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    console.log("请求拦截器执行...", config);

    // 添加自定义的请求头信息
    config.headers["x-custom-header-username"] = "xiaohui";
    config.headers["x-custom-header-password"] = "admin";

    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

响应拦截器:

/**
 * 添加响应拦截器
 * response: 响应对象
 */
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    // 2xx 范围内的状态码都会触发该函数。
    console.log("响应拦截器执行...", response);
    response.data.msg = "我是响应拦截器返回的消息";
    return response;
}, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});

5.6 sessionstorage和localstorage

localStorage和sessionStorage一样都是用来存储客户端临时信息的对象。他们均只能存储字符串类型的对象(虽然规范中可以存储其他原生类型的对象,但是目前为止没有浏览器对其进行实现)。

  • localStorage:生命周期是永久,除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。
  • sessionStorage:生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。

不同浏览器无法共享localStorage或sessionStorage中的信息。相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享sessionStorage的信息。

Tips:页面及标签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么他们之间是可以共享sessionStorage的。

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <script src="./js/jquery-3.5.1.min.js"></script>

</head>

<body>

<h3>Session Storage的值:</h3>

<p id="sessionStorageText"></p>

<h3>Local Storage的值:</h3>

<p id="localStorageText"></p>

<button id="setSessionStorageBtn">set sessionStorage</button>

<button id="setLocalStorageBtn">set localStorage</button>

<script>

    $(function (){
        // 页面加载完毕就获取localStorage和sessionStorage的值
        var sessionStorageText = sessionStorage.getItem("sessionStorageText");
        var localStorageText = localStorage.getItem("localStorageText");

        $("#sessionStorageText").html(sessionStorageText)
        $("#localStorageText").html(localStorageText)
    })

    // 设置sessionStorage的值
    $("#setSessionStorageBtn").click(function () {
        var value = prompt("请输入sessionStorage的值");
        sessionStorage.setItem("sessionStorageText", value);
        $("#sessionStorageText").html(value)
    });

    // 设置localStorage的值
    $("#setLocalStorageBtn").click(function () {
        var value = prompt("请输入localStorage的值");
        localStorage.setItem("localStorageText", value);
        $("#localStorageText").html(value)
    });

</script>

</body>

</html>

查看浏览器的SessionStorage和LocalStorage的值:

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐