后端小白程序员的axios学习笔记

axios

写在前面

本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!

一、入门了解

1.1、什么是axios?

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

简单来说,就是我们可以借助axios发送一些http请求,例如我们最熟悉的GET、POST请求,再例如我们学习过的Ajax异步请求。那么说到这里,你肯定会想起JQuery,因为我们目前在前端页面中都还是使用JQuery的库向服务器发送请求。

1.2、为什么学习axios?

随着前端框架Vue、React的出现,催生了axios这种轻量级的库,我们不必再使用庞大而复杂JQuery库来完成请求发送的操作。并且axios的功能更强大,并且支持Promise API。目前主流的前端框架(Vue、React)都极力推荐axios,并且对其做了积极的整合。

axios特性:

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

axios中文网|axios API 中文文档 | axios (axios-js.com)

1.3、前置内容与环境准备

在学习axios时,我们默认已经了解并学习了以下知识:

  • Promise
  • AJAX

1.3.1、json-server安装使用

既然是发送Http请求,那么我们就必须要有一个接收响应我们请求的服务端。这里推荐使用json-server
github仓库地址:typicode/json-server: Get a full fake REST API with zero coding in less than 30 seconds (seriously) (github.com)

0配置在30秒内创建一个虚假的REST API (REST是什么,学过后端的应该都懂,这里略过),直接开搞!

  1. 首先需要有node环境,然后创建项目文件夹后,在Terminal中打开文件夹,执行npm init进行项目初始化!

  2. 下载json-server到项目本地lib(你也可以选择全局安装)

    npm install --save-dev json-server
    
  3. 在项目根目录下创建db.json(这就相当于我们的数据库,请求的响应的数据都从这里面获取),可以按需修改,这是官网给的默认数据格式:

    {
      "posts": [
        { "id": 1, "title": "json-server", "author": "typicode" }
      ],
      "comments": [
        { "id": 1, "body": "some comment", "postId": 1 }
      ],
      "profile": { "name": "typicode" }
    }
    
  4. package.json中加上服务启动脚本

     "scripts": {
         "test": "echo \"Error: no test specified\" && exit 1",
    +    "server": "json-server --watch db.json"
     },
    

    如果是全局安装,可以在db.json所在目录下直接使用json-server --watch db.json启动服务

    image-20210526142631357
  5. 访问localhost:3000

  6. 发送请求,获取数据。例如GET localhost:3000/posts/1。现在你就可以将其作为一个虚拟的服务器进行请求访问了!(更多高级用法请参考官方文档!)

    image-20210526143403511

1.3.2、axios安装

npm安装使用

npm install axios --save

cdn引入

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

如果速度很慢,可以在BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务上获取axios的国内cdn

<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>
<!--或者-->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>

1.4、入门案例

这里我们结合使用vue,并使用axios完成从json-server获取数据:(引用了bootstrap组件库)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
      crossorigin="anonymous"
    ></script>
    
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js"></script>

    <div class="container" id="app">
      <div class="row">
        <div>
          <h1>Quick Start</h1>
          <hr />
        </div>
        <div class="col-2">
          <button type="button" class="btn btn-primary" @click="getAllPosts()">
            获取所有post
          </button>
        </div>
        <div class="col-2">
          <button type="button" class="btn btn-primary" @click="getComments()">
            获取comment/1
          </button>
        </div>
      </div>
      <hr />
      <div class="row">
        <div>
          <textarea class="form-control" rows="8">{{response}}</textarea>
        </div>
      </div>
    </div>
  </body>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        response: null,
      },
      methods: {
        getAllPosts() {
          axios.get('http://localhost:3000/posts').then((response) => {
            this.response = response.data
          })
        },
        getComments() {
          axios.get('http://localhost:3000/comments/1').then((response) => {
            this.response = response.data
          })
        },
      },
    })
  </script>
</html>

这段代码的核心内容就是:

axios.get('http://localhost:3000/posts').then(...)

效果展示:

axios-quickstart

首先我们可以看出,使用axios进行请求发送和响应处理的手段有浓浓的Promise的味道。那我是不是可以理解为,我们的Http请求发送任务是一个异步任务??

除了使用axios.get(url)这种方式,我们还可以像JQuery中那样对请求进行配置:

axios(url[, config])

axios({
    url: 'http://localhost:3000/posts',
    method: 'get',
    params: {
        id: 1,
    },
}).then(...)

这样进行详细配置后,发送的请求URL是:http://localhost:3000/comments?id=1


以上就是我们axios的一个简单入门使用案例,下面我们将从API、请求配置等多个方面进行学习。

二、axios API

2.1、请求方法

我们主要来看看axios有哪些常用的方法。前面我们使用过axios.get(url[,config])axios(url[,config])

随着RESTFul风格的流行,我们的请求方式不再拘泥于GET、POST,新的请求方式例如PUT、DELETE、PATCH得以大量使用,axios也为这些请求方式提供了方法别名:

axios.request(config)

axios.get(url[, config])

axios.delete(url[, config])

axios.head(url[, config])

axios.options(url[, config])

axios.post(url[, data[, config]])

axios.put(url[, data[, config]])

axios.patch(url[, data[, config]])

所以他们都是用于发请求的,并且请求的配置都没有差别,你可以根据需要选择对应请求方式的axios请求方法。我们这里就以post为例,向服务器发送一个添加数据的请求:

const app = new Vue({
    el: '#app',
    methods: {
        addComment() {
            axios
                .post('http://localhost:3000/comments', {
                id: 2,
                body: '继续加油!!fighting!',
                postId: 2,
            })
                .then(console.log)
        },
    },
})

其他方法除了在请求方式上存在区别外,其他的配置都一样,按需选用即可!

2.2、并发请求

axios.all(),有点类似promise.all(),多个请求发送,然后返回结果。

const app = new Vue({
    el: '#app',
    methods: {
        getPostsAndComments() {
            // 请求1:获取所有的post
            let posts = function () {
                return axios
                    .get('http://localhost:3000/posts')
                    .then((response) => response.data)
            }
            // 请求2: 获取所有的comment
            let comments = function () {
                return axios
                    .get('http://localhost:3000/comments')
                    .then((response) => response.data)
            }

            axios
                .all([posts(), comments()])
                .then(console.log)
                .catch((reason) => console.log(reason))
        },
    },
})

只要all的参数列表中,一个请求失败那么就不会得到最终结果,并且失败异常将会被捕获输出。
全部成功后,所有请求的结果,将按请求参数的顺序放置在一个数组中。

效果演示:

image-20210526175532682

2.3、创建axios实例

axios.create([config])

前面我们都是直接使用axios的方法进行请求发送。当我们需要给多台服务器发送请求,并且请求的配置极其复杂时,我们可以针对每台服务器创建一个axios对象,将进行配置,然后以后通过实例方法发送请求即可。

let instance = axios.create({
    baseURL: 'https://localhost:3000/api/',
    timeout: 1000,
    // ... 其他配置
})

let instance2 = axios.create({
    baseURL: 'http://localhost:8080/movie/222'
    // ... 其他配置
})

axios实例的方法,和我们直接使用axios调用的方法是相同的!

三、请求配置

这章我们需要了解axios请求的一些配置。作为一个以发送请求为主要功能的库,对请求进行配置是特别重要的!

中文官网上有对配置的详细说明:axios | 请求配置

我们选择几个较为常用的配置进行说明:

  • url:被请求的服务器URL

  • method:请求方式,默认为get

  • baseURL:作为请求的URL的基础部分,将自动拼接在url前面。(常用于axios实例中配置)可以避免重复编写URL。

  • transformRequest/Response

    • transformRequest: 用于在发送请求之前,对请求数据进行修改。(只能用于POST、PUT、PATCH请求)
    • tranformResponse: 用于在接收到响应前(传给then之前),对响应的数据做转换
  • header:请求头,可以通过kv形式进行修改。

  • params:请求时发送的URL参数。(与后面的data区分)

  • paramsSerializer:params的序列化函数,它决定了params如何拼接到URL上去。(一般不做修改)

  • data:请求的主体数据,并不会被拼接到URL上,但是能被服务器解析。只适用于PUT、PATCH、POST请求

  • timeout :设置请求超时时间,超过此限制后请求将被中断,0为无超时时间。

  • cancelToken:用于取消请求,后续我们会详细说明如何取消请求。(常用于请求按钮防抖)

四、响应结构说明

我们通过get等请求获取到的响应可以通过then向下传递并进行处理。我们需要了解响应数据的结构,以便于我们更好地利用响应数据!

image-20210526202209648

  • data: 这是我们响应数据的主体,是服务器响应我们请求后返回的数据

  • status: 服务器响应的HTTP状态码

  • statusText: 服务器响应的HTTP状态信息

  • headers: 响应头

  • config: 服务器响应的请求 的对应配置信息

  • request: 响应的具体请求对象

五、默认配置

前面我说使用axios实例来单独管理我们的配置,具体如何管理,这里我们就会详细说明

5.1、配置全局默认配置

使用axios.default.xxx = xxxx,就可以将请求配置中的xxx全局性配置为xxxx。此时如果你创建的axios实例中没有对其配置项进行配置,将使用这个全局默认配置!如果配置了,那就涉及到配置优先级的问题,后面再说!

axios.defaults.baseURL = 'http://localhost:3000'
axios.defaults.timeout = 2000

let posts = axios.create()
posts.get('/posts').then(console.log)
image-20210526204432638

通过输出的response对象来看,创建的axios对象确实使用了我们全局配置的baseURL和timeout!

5.2、实例的默认配置

为实例配置默认配置有两种方式:

  1. 在使用axios.create创建时作为参数传入
  2. 使用instance.defaults.xxx进行设置
let posts = axios.create({
    timeout: 3000,
})

posts.defaults.timeout = 4000

对于同一个配置项,当两种配置方式同时出现时,后者将会覆盖前者!

5.3、配置的优先级

在进行请求配置时,会按照一下顺序进行搜索:

  1. lib/default.js中axios库的默认值
  2. 实例的默认值
  3. 请求方法中传入的config参数

后面的都会将前面的配置覆盖掉!所以说优先级最高的就是请求方法中的config参数

其次是实例的默认配置最后是axios全局的默认配置(即axios库的默认配置)

axios.defaults.baseURL = 'http://localhost:3000'
axios.defaults.timeout = 2000

let posts = axios.create({
    timeout: 3000,
})

posts.defaults.timeout = 4000

posts.get('/posts', { timeout: 5000 }).then(console.log)

这样配置后,最终响应结果中的config,timeout值为5000。

image-20210526210152845

感兴趣的可以尝试层层删除timeout的配置,来验证一下配置优先级!

六、拦截器

个人有幸之前学习过Flume,与Flume拦截器功能一样,它们的功能都是将数据先进行拦截,然后进行处理后再决定是否放行。并不是简单的拦截打回。

axios的拦截器分为请求拦截器和响应拦截器。它们所做的工作分别是:将请求/响应数据,拦截进行必要的处理后发送/返回。

添加拦截器代码示例:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
});

这个结构是不是感觉似曾相识,的确,它和我们之前在Promise中使用的then方法极其类似!它会先根据请求/响应状态执行对应的回调函数。如果请求拦截器,出错返回了rejected Promise对象,那么响应拦截器就会执行失败回调!

拦截器的调用顺序,最先声明的请求拦截器会最后调用,而响应拦截器则是按声明顺序调用!要搞清楚为什么需要阅读axios实现源码。

七、错误处理

既然采用PromiseAPI,那么进行错误处理只需要在调用链后加上.catch()即可完成对错误的捕捉和处理!

axios.get('http://localhost:3000/posts')
    .then(console.log)
    .catch(function (error) {
    	// ... 异常处理
	});

在请求配置中指定错误处理的范围:请求配置中有一项名为validateStatus的配置项,是用于验证请求状态的函数。这是默认实现:

validateStatus: function (status) {
    return status >= 200 && status < 300; // default
}

当状态码 200 < status < 300 则视为请求状态正常,否则视为请求异常!

你可以对异常请求的判断范围做修改:

validateStatus: function (status) {
    return status < 500
}

八、取消请求

为什么会有取消请求的需求?试想一个按钮点击一下发送一个请求,如果用户反复点击,就会一下发送很多请求,这样会给客户端造成压力,如果我们能够在用户第二次点击请求按钮时,将先前还未响应的请求取消,那么多次点击也就只有一次响应!这样就实现了按钮防抖!

而要实现请求取消,需要一个请求配置:cancelToken

需要使用json-server的启动选项-d来指定服务器响应时间,否则来不及取消请求!(-d 3000为响应时间 3s)

方式一:使用 CancelToken.source 工厂方法创建 cancel token

const CancelToken = axios.CancelToken
const source = CancelToken.source()

axios.defaults.baseURL = 'http://localhost:3000'

const app = new Vue({
    el: '#app',
    methods: {
        sendRequest() {
            axios
                .request('/posts', {
                // 将source.token设置为cancelToken
                cancelToken: source.token,
            })
                .then(console.log)
                .catch(console.log)
        },
        cancelRequest() {
            source.cancel('Request was canceled')
        },
    },
})

但是这种方式好像只能取消一次!?

方式二:传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token

const CancelToken = axios.CancelToken

// 变量,用于接收cancel函数
let cancel = null
axios.defaults.baseURL = 'http://localhost:3000'

const app = new Vue({
    el: '#app',
    methods: {
        sendRequest() {
            axios
                .request('/posts', {
                // 传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token
                cancelToken: new CancelToken(function (c) {
                    // 含cancel函数参数赋值给变量
                    cancel = c
                }),
            })
                .then((response) => {
                console.log(response.data)
                // 已完成响应销毁cancel函数
                cancel = null
            })
                .catch(console.log)
        },
        cancelRequest() {
            if (cancel !== null) {
                // 执行cancel函数,清除token
                cancel()
            }
        },
    },
})

axios-cancel

当前效果还是使用两个按钮来实现,我们将俩个按钮的代码中和到一起,即可实现按钮防抖:

sendRequest() {
    // 先判断cancel函数是否有效
    if (cancel !== null) {
        // 调用取消请求
        cancel()
    }

    axios
        .request('/posts', {
        // 传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token
        cancelToken: new CancelToken(function (c) {
            // 含cancel函数参数赋值给变量
            cancel = c
        }),
    })
        .then((response) => {
        console.log(response.data)
        // 已完成响应,销毁cancel函数
        cancel = null
    })
        .catch(console.log)
},

axios-cancel2

使用这种方式,我们每个请求都会new一个cancelToken,以及对应的cancel函数,所以每次点击都不相影响。

而方式一使用的cancelToken都是同一个,一旦cancel后,就无法变更了。所以只能取消一次。

8.1、请求取消原理

我们使用第二种方式创建cancelToken时,调用了new CancelToken(),并且我们传入了一个函数,函数中我们将函数的参数c,赋值给了一个变量。而这个参数c,则是我们所说的cancel函数。

请求取消其原理还是使用了Promise,当我们调用对应的函数时,会改变Promise对象的State,从而触发对应的请求取消操作,最核心的取消操作就是request.abort()(这是ajax取消请求的方法!)

这里是CancelToken的源码:/lib/cancel/CancelToken.js

image-20210527154438003
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
});

这段代码执行后,this.promise的命运被交给了resolvePromise,一旦resolvePromise()被执行,this.promise的State就会变为fulfilled

然后看下面的executor,这就是我们new CancelToken时传入的函数function(c) { ... }

executor调用时又传入了一个function cancel(message)作为参数,也就是我们的参数c,此时我们的c就可以当做函数调用了!而刚好在我们自己的代码中,我们将c又赋值给了一个变量x

那我们再来看看这个cancel函数:

function cancel(message) {
    if (token.reason) {
        // Cancellation has already been requested
        return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
}

首先判断是否已经被要求取消,若是直接返回,否则先设置取消的reason,表示已经进行过请求取消了,然后调用resolvePromise(),并将reason传入,这相当于在Promise中调用了resolve()。那么也就是说在我们的代码中通过这个x()就可以改变CancelToken对象内部属性promise的状态!执行后this.promise的State将被设置为fulfilled!但是到这还没完,还没有调用request.abort()正式取消请求!

再来到./lib/adapters/xhr.js,看到这段代码:

image-20210527155916932

首先会在config中查看我们是否设置了cancelToken,如果设置了,为我们设置的cancelToken的内部的promise对象加入一个订阅者(或者处理程序),但是注意这里then()只设置了resolve,即针对fulfilled状态的回调:

function onCanceled(cancel) {
    if (!request) {
        return;
    }

    request.abort();
    reject(cancel);
    // Clean up request
    request = null;
}

这就串上了我们CancelToken的代码,这里的config.cancelToken.promise和CancelToken中的this.promise就是同一个东西!当我们调用cancel()方法后,resolvePromise会将promise的State置为fulfilled,然后这里的处理程序接收到promise转态变化,调用request.abort()取消了请求,并将请求对象清除!

这种“控制权”的来回传递,对用户封装了大部分代码。(代码有点绕,需要反复理解梳理!)

方式一源码:

方式一中,我们使用source.token作为我们请求的cancelToken。通过看其源码,可以发现和方式二没有太大区别,只是将new CancelToken()的过程进行了封装!

image-20210527161452711

但是很显然这是一个一次性的token,使用完毕后,就必须使用source()创建新的!而使用方式二,每个请求都会new一个CancelToken!


九、vue-axios

在vue中使用axios:

  1. npm 安装axios、axios-vue

    npm install --save axios vue-axios
    
  2. 引入依赖,并使用

    import Vue from 'vue'
    import axios from 'axios'
    import VueAxios from 'vue-axios'
    
    Vue.use(VueAxios, axios)
    
  3. 它会像vue-router一样,向每个组件注入属性。

    再组件内的script代码中,使用this.$httpthis.axiosaxios就可以作为axios实例进行使用,使用请求方法发送请求了!!(直接使用axios时,要通过import在script代码中导入)

    methods: {
        sendRequest() {
            axios.defaults.baseURL = 'http://localhost:3000'
            axios.get('/posts')
                .then((response) => {
                console.log(response.data)
            })
        }
    }
    

但是他们三者有什么联系,axios的请求配置范围目前我们还不清楚。

目前只知道,当我们在main.js中使用axios.default.xxx=x进行全局默认配置后,在其他组件中无论是使用this.axios还是this.$http,都会生效!

你也可以使用axios.create创建axios实例进行配置后并export,然后在其他组件中impor进行使用!

现在我坚信他们仨就是同一个东西:(vue-axios/src/index.js)

image-20210527171915925

(不知道我看的位置对不对~~*)
ipt代码中,使用this.$httpthis.axiosaxios就可以作为axios实例进行使用,使用请求方法发送请求了!!(直接使用axios时,要通过import在script代码中导入)

methods: {
    sendRequest() {
        axios.defaults.baseURL = 'http://localhost:3000'
        axios.get('/posts')
            .then((response) => {
            console.log(response.data)
        })
    }
}

但是他们三者有什么联系,axios的请求配置范围目前我们还不清楚。

目前只知道,当我们在main.js中使用axios.default.xxx=x进行全局默认配置后,在其他组件中无论是使用this.axios还是this.$http,都会生效!

你也可以使用axios.create创建axios实例进行配置后并export,然后在其他组件中impor进行使用!

现在我坚信他们仨就是同一个东西:(vue-axios/src/index.js)

image-20210527171915925

(不知道我看的位置对不对~~*)

posted @   5akura  阅读(282)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示