axios笔记(二) 深入了解axios
1. 介绍
- 前端最流行的ajax请求库
- react / vue官方推荐使用axios发送ajax请求
- axios仓库
2. axios特点
- 基于promise的异步ajax请求库
- 浏览器端 / node端都可以使用
- 支持请求 / 响应拦截器
- 支持请求取消
- 请求 / 响应数据转换
- 批量发送多个请求
3. axios常用语法
-  axios(config):最本质的能发任何类型请求的方式 
-  axios.get(url, config):发get请求 
-  axios.put(url, config):发put请求 
-  axios.defaults.xxx:请求的默认全局配置,如baseURL 
-  axios.interceptors.request.use():添加请求拦截器 
-  axios.interceptors.response.use():添加响应拦截器 
-  axios.create(config):新建一个 axios 实例(没有以下的功能) 
-  axios.Cancel():用于创建取消请求的错误对象 
-  axios.CancelToken():用于创建取消请求的token对象 
-  axios.isCancel():判断是否是一个取消请求的错误 
-  axios.all(promises):用于批量执行多个异步错误 
3.1 axios简单使用
<!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>
</head>
<body>
  <button onclick="testGet()">GET请求</button>
  <button onclick="testPost()">POST请求</button>
  <script src="./node_modules/axios/dist/axios.min.js"></script>
  <script>
    axios.defaults.baseURL = 'http://localhost:3000' // 设置请求的基址,后面就不需要写完整的路径了
    // GET请求: 服务端获取数据
    const testGet = () => {
      axios({
          url: '/posts',
          method: 'GET',
          params: {
            id: 1
          }
        })
        .then(response => {
            console.log(response)
          },
          error => {
            alert(error.message)
          })
    }
    // POST请求: 服务端增加数据
    const testPost = () => {
      axios({
          url: '/posts',
          method: 'POST',
          data: {
            title: 'axios',
            author: 'clz'
          }
        })
        .then(response => {
            console.log(response)
          },
          error => {
            alert(error.message)
          })
    }
  </script>
</body>
</html>
3.2 axios.create(config)
简单使用
const instance1 = axios.create({
  baseURL: 'http://localhost:3000'
})
// 使用instance发送请求
instance1({
  url: '/posts'
})
可以发现用法和axios()很像,和下面一样效果
axios.defaults.baseURL = 'http://localhost:3000'
axios({
  url: '/posts'
})
那么,axios.create(config)肯定得有它的过人之处,否则,早就会被淘汰掉了。
先来一个情景:我们要向端口3000发送get请求,要向端口4000发送post请求
通过axios.create(config)实现:
const instance1 = axios.create({
  baseURL: 'http://localhost:3000'
})
// 使用instance发送请求
instance1({
  url: '/posts'
})
const instance2 = axios.create({
  baseURL: 'http://localhost:4000'
})
// 使用instance发送请求
instance2({
  url: '/posts',
  method: 'POST'
})
直接通过axios()实现:
axios.defaults.baseURL = 'http://localhost:3000'
axios({
  url: '/posts'
})
axios.defaults.baseURL = 'http://localhost:4000'
axios({
  url: '/posts',
  method: 'POST'
})
可能有人会提出疑问:不是一样都能实现吗?但是,如果我需要最后在向端口3000再发送一次GET请求的话。
让我们再看一下代码:
通过axios.create(config)实现:
const instance1 = axios.create({
  baseURL: 'http://localhost:3000'
})
instance1({
  url: '/posts'
})
const instance2 = axios.create({
  baseURL: 'http://localhost:4000'
})
instance2({
  url: '/posts',
  method: 'POST'
})
instance1({
  url: '/posts'
})
直接通过axios()实现:
axios.defaults.baseURL = 'http://localhost:3000'
axios({
  url: '/posts'
})
axios.defaults.baseURL = 'http://localhost:4000'
axios({
  url: '/posts',
  method: 'POST'
})
axios.defaults.baseURL = 'http://localhost:3000'
axios({
  url: '/posts'
})
比较上面的两种情况,就能发现**axios.create(config)**的好处了。因为是新建axios实例,所以3000和4000两个端口分别使用的是不同的axios实例。所以能够很好地各司其责。而axios()则不能,仅仅只是简单地修改baseURL,都需要每次发送请求前重新修改,还是未考虑异步的情况。这么一看,在这种情况下,**axios.create()**新建axios实例还是很香的。
3.3 拦截器及运行流程
3.3.1 添加请求拦截器
axios.interceptors.request.use(function (config) {
  // Do something before request is sent
  return config;			// 用于把任务串联起来
}, function (error) {
  // Do something with request error
  return Promise.reject(error);		// 一定要返回错误,不让之后能进入成功的流程
});
3.3.2 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
  }, function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  });
3.3.3 拦截器简单使用
<!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>拦截器简单使用</title>
</head>
<body>
  <script src="./node_modules/axios/dist/axios.min.js"></script>
  <script>
    // 添加请求拦截器(回调函数,成功的必须返回config:用于把任务串联起来)
    axios.interceptors.request.use(
      config => {
        console.log('request interceptor1 onResolved()')
        return config
      },
      error => {
        console.log('request interceptor1 onRejected()')
        return Promise.reject(error) // 一定要返回错误,不让之后能进入成功的流程
      }
    )
    axios.interceptors.request.use(
      config => {
        console.log('request interceptor2 onResolved()')
        return config
      },
      error => {
        console.log('request interceptor2 onRejected()')
        return Promise.reject(error)
      }
    )
    // 添加响应拦截器(回调函数,成功的必须返回response:用于把任务串联起来)
    axios.interceptors.response.use(
      response => {
        console.log('response interceptor1 onResolved()')
        return response
      },
      error => {
        console.log('response interceptor1 onRejected()')
        return Promise.reject(error)
      }
    )
    axios.interceptors.response.use(
      response => {
        console.log('response interceptor2 onResolved()')
        return response
      },
      error => {
        console.log('response interceptor2 onRejected()')
        return Promise.reject(error)
      }
    )
    axios.get('http://localhost:3000/posts')
      .then(response => {
        console.log('data: ', response.data)
      })
      .catch(error => {
        console.log('error: ', error.message)
      })
  </script>
</body>
</html>

先发送请求,再接收响应。所以会先触发请求拦截器,再触发响应拦截器,经过响应拦截器后才能得到数据
3.3.4 取消请求
express知识:Express笔记: clz
先搭建一个服务器:
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())  // 使用cors,允许跨域
app.use(express.json())   // 解析Content-Type为 application/json格式的请求体
app.get('/product1', (req, res) => {
  setTimeout(() => {
    res.send([
      {
        id: 1,
        name: 'product1'
      }
    ])
  }, 2000)
})
app.listen(3000, () => {
  console.log('http://localhost:3000')
})
开启服务器,node server.js或nodemon server.js(支持热更新)
取消请求的简单使用:
-  先定义一个变量 cancel,用于保存取消请求的函数
-  设置请求路径、请求方法时,还要设置属性 cancelToken,值为一个CancelToken对象,CancelToken类的构造函数的参数是用于请求的函数cancelToken: new axios.CancelToken((c) => { // c是用于取消当前请求的函数 cancel = c // 保存取消请求函数,用于之后取消请求 })
-  在需要请求的地方,调用保存的取消请求的函数 
<!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>取消请求</title>
</head>
<body>
  <button onclick="getProducts1()">获取商品列表1</button><br>
  <button onclick="cancelReq()">取消请求</button>
  <script src="./node_modules/axios/dist/axios.js"></script>
  <script>
    let cancel // 用于保存取消请求的函数
    const getProducts1 = () => {
      axios({
        url: 'http://localhost:3000/products1',
        cancelToken: new axios.CancelToken((c) => { // c是用于取消当前请求的函数
          cancel = c // 保存取消请求函数,用于之后取消请求
        })
      }).then(response => {
        cancel = null // 请求结束后保存的取消请求的函数不在需要保存
        console.log('请求1成功: ', response.data)
      }, error => {
        cancel = null
        console.log('请求1失败: ', error.message, error)
      })
    }
    const cancelReq = () => {
      if (typeof cancel === 'function') {
        cancel('强制取消请求1')
      } else {
        console.log('没有可以取消的请求')
      }
    }
  </script>
</body>
</html>
另外,取消请求的函数可以传参,传的参数将变成请求失败时,Cancel对象的message(这个时候并不是Error对象)
取消请求优化:发送请求前取消掉未完成的请求
在点击事件最前面添加判断
if (typeof cancel === 'function') {
  cancel('取消请求')
}
这里会出现一个问题,如果连续发送三个请求(在收到响应之前),会发现,第三个请求没有取消掉前一个未完成的请求
为什么会这样呢?看下下面的流程图就可能可以迎刃而解了(有错可评论指出)

为了解决上面的问题,这时候就需要使用axios.isCancel(error)判断是不是取消请求导致的请求失败,如果是,则此时不需要把cancel变为null
<!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>取消请求</title>
</head>
<body>
  <button onclick="getProducts1()">获取商品列表1</button><br>
  <button onclick="cancelReq()">取消请求</button>
  <script src="./node_modules/axios/dist/axios.js"></script>
  <script>
    let cancel // 用于保存取消请求的函数
    const getProducts1 = () => {
      if (typeof cancel === 'function') {
        cancel('取消请求')
      }
      axios({
        url: 'http://localhost:3000/products1',
        cancelToken: new axios.CancelToken((c) => { // c是用于取消当前请求的函数
          cancel = c // 保存取消请求函数,用于之后取消请求
        })
      }).then(response => {
        cancel = null // 请求结束后保存的取消请求的函数不在需要保存
        console.log('请求1成功: ', response.data)
      }, error => {
        if (axios.isCancel(error)) {
          console.log('请求1失败: ', error.message)
        } else {
          cancel = null
          console.log('请求1失败: ', error.message)
        }
      })
    }
    const cancelReq = () => {
      if (typeof cancel === 'function') {
        cancel('强制取消请求1')
      } else {
        console.log('没有可以取消的请求')
      }
    }
  </script>
</body>
</html>

通过拦截器再优化:增加代码可复用性
// 请求拦截器
axios.interceptors.request.use(config => {
  if (typeof cancel === 'function') {
    cancel('取消请求')
  }
  config.cancelToken = new axios.CancelToken((c) => { // c是用于取消当前请求的函数
    cancel = c // 保存取消请求函数,用于之后取消请求
  })
  return config
})
// 响应拦截器
axios.interceptors.response.use(response => {
  cancel = null
  return response
}, error => {
  if (axios.isCancel(error)) {
    console.log('请求取消: ', error.message)
    return new Promise(() => {}) // 中断Promise链。因为返回一个pending状态的对象时,后续的回调就不能执行了。
    // 因为后面的回调函数只有在状态发生变化时才能执行。
  } else {
    cancel = null
    return Promise.reject(error) // 将错误向下传递
  }
})
完整代码:
<!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>取消请求</title>
</head>
<body>
  <button onclick="getProducts1()">获取商品列表1</button><br>
  <button onclick="getProducts2()">获取商品列表2</button><br>
  <button onclick="cancelReq()">取消请求</button>
  <script src="./node_modules/axios/dist/axios.js"></script>
  <script>
    // 请求拦截器
    axios.interceptors.request.use(config => {
      if (typeof cancel === 'function') {
        cancel('取消请求')
      }
      config.cancelToken = new axios.CancelToken((c) => { // c是用于取消当前请求的函数
        cancel = c // 保存取消请求函数,用于之后取消请求
      })
      return config
    })
    // 响应拦截器
    axios.interceptors.response.use(response => {
      cancel = null
      return response
    }, error => {
      if (axios.isCancel(error)) {
        console.log('请求取消: ', error.message)
        return new Promise(() => {}) // 中断Promise链。因为返回一个pending状态的对象时,后续的回调就不能执行了。
        // 因为后面的回调函数只有在状态发生变化时才能执行。
      } else {
        cancel = null
        return Promise.reject(error) // 将错误向下传递
      }
    })
    let cancel // 用于保存取消请求的函数
    const getProducts1 = () => {
      axios({
        url: 'http://localhost:3000/products1',
      }).then(
        response => {
          console.log('请求1成功: ', response.data)
        },
        error => {
          console.log('请求1失败: ', error.message)
        })
    }
    const getProducts2 = () => {
      axios({
        url: 'http://localhost:3000/products1',
      }).then(
        response => {
          console.log('请求2成功: ', response.data)
        },
        error => {
          console.log('请求2失败: ', error.message)
        })
    }
    const cancelReq = () => {
      if (typeof cancel === 'function') {
        cancel('强制取消请求1')
      } else {
        console.log('没有可以取消的请求')
      }
    }
  </script>
</body>
</html>










