准备工作
1.作为一个前端开发工程师,在后端还没有准备好的时候,不可避免的要使用mock的数据。很多时候,我们并不想使用简单的静态数据,而是希望自己起一个本地的mock-server来完全模拟请求以及请求回来的过程。json-server 就来啦~我们只需要提供一个json文件,或者写几行简单的js脚本就可以模拟出RESTful API的接口
-
安装json-server
npm install -g json-server -
创建db.json 在一个文件夹下新建一个db.json文件
//db.json
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
- 启动json-server 在当前文件夹下输入如下命令:
json-server db.json
一: Axios的理解与使用
一、Axios的理解与使用
Ⅰ-axios 是什么?
- 前端最流行的 ajax 请求库
- react/vue 官方都推荐使用 axios 发 ajax 请求
- 文档: github.com/axios/axios
Ⅱ-axios 特点
- 基于 xhr + promise 的异步 ajax 请求库
- 浏览器端/node 端都可以使用
- 支持请求/响应拦截器
- 支持请求取消
- 请求/响应数据转换
- 批量发送多个请求
Ⅲ-axios 常用语法
- axios(config):
通用/最本质的发任意类型请求的方式- axios(url[, config]): 可以只指定 url 发 get 请求
- axios.request(config): 等同于 axios(config)
- axios.get(url[, config]): 发 get 请求
- axios.delete(url[, config]): 发 delete 请求
- axios.post(url[, data, config]): 发 post 请求
- axios.put(url[, data, config]): 发 put 请求
- axios.defaults.xxx: 请求的默认全局配置
- axios.interceptors.request.use(): 添加请求拦截器
- axios.interceptors.response.use(): 添加响应拦截器
- axios.create([config]): 创建一个新的 axios(它没有下面的功能)
- axios.Cancel(): 用于创建取消请求的错误对象
- axios.CancelToken(): 用于创建取消请求的 token 对象
- axios.isCancel(): 是否是一个取消请求的错误
- axios.all(promises): 用于批量执行多个异步请求
- axios.spread(): 用来指定接收所有成功数据的回调函数的方法
Ⅳ-原理图
Ⅴ-难点语法的理解和使用
1、axios.create(config)
- 根据指定配置创建一个新的 axios, 也就就每个新 axios 都有自己的配置
- 新 axios 只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的
运用场景:
(1):项目中有部分接口需要的配置与另一部分接口需要的配置不太一样
//创建实例对象
const one = axios.create({
baseURL: 'http://apiopen.top',
timeout:2000
})
const two = axios.create({
baseURL: 'http://another.bo',
timeout:2000
})
//发送请求
one({
url:'/1',
}).then(response=>{
console.log(response)
})
two({
url:'/2',
}).then(response=>{
console.log(response)
})
2、拦截器函数/ajax 请求/请求的回调函数的调用顺序
1.请求流程:请求拦截器2 => 请求拦截器1 => 发ajax请求 => 响应拦截器1 => 响应拦截器 2 => 请求的回调
2.此流程是通过 promise 串连起来的, 请求拦截器传递的是 config, 响应 拦截器传递的是 response
<script>
// Promise
// 设置请求拦截器 config 配置对象
axios.interceptors.request.use(function (config) {
console.log('请求拦截器 成功 - 1号');
//修改 config 中的参数
config.params = {
a: 100
};
return config;
}, function (error) {
console.log('请求拦截器 失败 - 1号');
return Promise.reject(error);
});
axios.interceptors.request.use(function (config) {
console.log('请求拦截器 成功 - 2号');
//修改 config 中的参数
config.timeout = 2000;
return config;
}, function (error) {
console.log('请求拦截器 失败 - 2号');
return Promise.reject(error);
});
// 设置响应拦截器
axios.interceptors.response.use(function (response) {
console.log('响应拦截器 成功 1号');
return response;
}, function (error) {
console.log('响应拦截器 失败 1号')
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
console.log('响应拦截器 成功 2号')
return response;
}, function (error) {
console.log('响应拦截器 失败 2号')
return Promise.reject(error);
});
//发送请求
axios({
method: 'GET',
url: 'http://localhost:3000/posts'
}).then(response => {
console.log('自定义回调处理成功的结果');
console.log(response);
});
</script>
3、取消请求
1.流程:配置 cancelToken 对象->1. 缓存用于取消请求的 cancel 函数-> 在后面特定时机调用 cancel 函数取消请求
使用:实现两个按钮,一个取消请求,一个发送请求
<script>
//获取按钮
const btns = document.querySelectorAll('button');
//2.声明全局变量
let cancel = null;
//发送请求
btns[0].onclick = function () {
//检测上一次的请求是否已经完成
if (cancel !== null) {
//取消上一次的请求
cancel();
}
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
//1. 添加配置对象的属性
cancelToken: new axios.CancelToken(function (c) {
//3. 将 c 的值赋值给 cancel
cancel = c;
})
}).then(response => {
console.log(response);
//将 cancel 的值初始化
cancel = null;
})
}
//绑定第二个事件取消请求
btns[1].onclick = function () {cancel(); }
</script>
Ⅵ-默认配置
//默认配置
axios.defaults.method = 'GET';//设置默认的请求类型为 GET
axios.defaults.baseURL = 'http://localhost:3000';//设置基础 URL
axios.defaults.params = {id:100};
axios.defaults.timeout = 3000;//
btns[0].onclick = function(){
axios({
url: '/posts'
}).then(response => {
console.log(response);
})
}
二、Axios的难点问题
Ⅰ- axios 与 Axios 的关系
- 从
语法上来说: axios 不是 Axios 的实例 - 从
功能上来说: axios 是 Axios 的实例 - axios 是
Axios.prototype.request函数 bind()返回的函数 - axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios 对象上所有属性
Ⅲ- instance 与 axios 的区别?
-
相同:
(1) 都是一个能发任意请求的函数: request(config)
(2) 都有发特定请求的各种方法: get()/post()/put()/delete() (3) 都有默认配置和拦截器的属性: defaults/interceptors
-
不同:
(1) 默认配置很可能不一样
(2) instance 没有 axios 后面添加的一些方法:create()/CancelToken()/all()
Ⅳ-axios请求的整体流程
-
整体流程: request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
-
request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise
-
dispatchRequest(config): 转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数 据. 返回 promise
-
xhrAdapter(config): 创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise
-
流程图:
Ⅴ-axios 的请求/响应拦截器是什么?
- 请求拦截器: Ⅰ- 在真正发送请求前执行的回调函数 Ⅱ- 可以对请求进行检查或配置进行特定处理 Ⅲ- 成功的回调函数, 传递的默认是 config(也必须是)Ⅳ- 失败的回调函数, 传递的默认是 error
- 响应拦截器 Ⅰ- 在请求得到响应后执行的回调函数 Ⅱ- 可以对响应数据进行特定处理 Ⅲ- 成功的回调函数, 传递的默认是 response Ⅳ- 失败的回调函数, 传递的默认是 error
Ⅵ-axios 的请求/响应数据转换器是什么?
1. 请求转换器: 对请求头和请求体数据进行特定处理的函数
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
2. 响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数
response.data = JSON.parse(response.data)
Ⅶ- 如何取消未完成的请求?
1. 当配置了 cancelToken 对象时, 保存 cancel函数
(1) 创建一个用于将来中断请求的 cancelPromise
(2) 并定义了一个用于取消请求的 cancel 函数
(3) 将 cancel 函数传递出来
1. 调用 cancel()取消请求
(1) 执行 cacel 函数, 传入错误信息 message
(2) 内部会让 cancelPromise 变为成功, 且成功的值为一个 Cancel 对象
(3) 在 cancelPromise 的成功回调中中断请求, 并让发请求的 proimse 失败, 失败的 reason 为 Cancel 对象
三、Axios源码模拟实现
Ⅰ- axios 的创建过程模拟实现
<script>
//构造函数
function Axios(config) {
//初始化
this.defaults = config; //为了创建 default 默认属性
this.intercepters = {
request: {},
response: {}
}
}
//原型添加相关的方法
Axios.prototype.request = function (config) {
console.log('发送 AJAX 请求 请求的类型为 ' + config.method);
}
Axios.prototype.get = function (config) {
//get内部能发送ajax请求的原因是内部调用了request方法
return this.request({
method: 'GET'
});
}
Axios.prototype.post = function (config) {
return this.request({
method: 'POST'
});
}
//声明函数
function createInstance(config) {
//实例化一个对象
let context = new Axios(config); // context.get() context.post() 但是不能当做函数使用 context() X
//创建请求函数
let instance = Axios.prototype.request.bind(
context); // instance 是一个函数 并且可以 instance({}) 此时 instance 不能 instance.get X
//将 Axios.prototype 对象中的方法添加到instance函数对象中,才可以instance.get....
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key].bind(context); // this始终指向实例,this.default this.interceptors
});
//为 instance 函数对象添加属性 default 与 interceptors
Object.keys(context).forEach(key => {
instance[key] = context[key];
});
return instance;
}
let axios = createInstance();
//发送请求
// axios({method:'POST'});
axios.get({});
axios.post({});
</script>
Ⅱ-axios发送请求过程详解
- 整体流程: request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
- request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise
- dispatchRequest(config): 转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数 据. 返回 promise
- xhrAdapter(config): 创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise
<script>
// axios 发送请求 axios Axios.prototype.request bind
//1. 声明构造函数
function Axios(config) {
this.config = config;
}
Axios.prototype.request = function (config) {
//发送请求
//创建一个 promise 对象
let promise = Promise.resolve(config);
//声明一个数组
let chains = [dispatchRequest, undefined]; // undefined 占位
//调用 then 方法指定回调
let result = promise.then(chains[0], chains[1]);
//返回 promise 的结果
return result;
}
//2. dispatchRequest 函数
function dispatchRequest(config) {
//调用适配器发送请求
return xhrAdapter(config).then(response => {
//响应的结果进行转换处理
//....
return response;
}, error => {
throw error;
});
}
//3. adapter 适配器
function xhrAdapter(config) {
console.log('xhrAdapter 函数执行');
return new Promise((resolve, reject) => {
//发送 AJAX 请求
let xhr = new XMLHttpRequest();
//初始化
xhr.open(config.method, config.url);
//发送
xhr.send();
//绑定事件
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
//判断成功的条件
if (xhr.status >= 200 && xhr.status < 300) {
//成功的状态
resolve({
//配置对象
config: config,
//响应体
data: xhr.response,
//响应头
headers: xhr.getAllResponseHeaders(), //字符串 parseHeaders
// xhr 请求对象
request: xhr,
//响应状态码
status: xhr.status,
//响应状态字符串
statusText: xhr.statusText
});
} else {
//失败的状态
reject(new Error('请求失败 失败的状态码为' + xhr.status));
}
}
}
});
}
//4. 创建 axios 函数
let axios = Axios.prototype.request.bind(null);
axios({
method: 'GET',
url: 'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
Ⅲ-拦截器的模拟实现
- array.shift()该方法用于把数组的第一个元素从其中删除,并返回第一个元素的值
- 思路为先将拦截器的响应回调与请求回调都压入一个数组中,之后进行遍历运行
promise = promise.then(chains.shift(), chains.shift());通过循环使用promise的then链条得到最终的结果-->等式前面的promise将被最终的结果覆盖
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拦截器</title>
<!-- <script src="./node_modules/axios/dist/mine-axios.js"></script> -->
</head>
<body>
<script>
//构造函数
function Axios(config){
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}
//发送请求 难点与重点
Axios.prototype.request = function(config){
//创建一个 promise 对象
let promise = Promise.resolve(config);
//创建一个数组
const chains = [dispatchRequest, undefined];
//处理拦截器
//请求拦截器 将请求拦截器的回调 压入到 chains 的前面 request.handles = []
this.interceptors.request.handlers.forEach(item => {
chains.unshift(item.fulfilled, item.rejected);
});
//响应拦截器
this.interceptors.response.handlers.forEach(item => {
chains.push(item.fulfilled, item.rejected);
});
// console.log(chains);
//遍历
while(chains.length > 0){
//array.shift()
promise = promise.then(chains.shift(), chains.shift());
}
return promise;
}
//发送请求
function dispatchRequest(config){
//返回一个promise 队形
return new Promise((resolve, reject) => {
resolve({
status: 200,
statusText: 'OK'
});
});
}
//创建实例
let context = new Axios({});
//创建axios函数
let axios = Axios.prototype.request.bind(context);
//将 context 属性 config interceptors 添加至 axios 函数对象身上
Object.keys(context).forEach(key => {
axios[key] = context[key];
});
//拦截器管理器构造函数
function InterceptorManager(){
this.handlers = [];
}
InterceptorManager.prototype.use = function(fulfilled, rejected){
this.handlers.push({
fulfilled,
rejected
})
}
//以下为功能测试代码
// 设置请求拦截器 config 配置对象
axios.interceptors.request.use(function one(config) {
console.log('请求拦截器 成功 - 1号');
return config;
}, function one(error) {
console.log('请求拦截器 失败 - 1号');
return Promise.reject(error);
});
axios.interceptors.request.use(function two(config) {
console.log('请求拦截器 成功 - 2号');
return config;
}, function two(error) {
console.log('请求拦截器 失败 - 2号');
return Promise.reject(error);
});
// 设置响应拦截器
axios.interceptors.response.use(function (response) {
console.log('响应拦截器 成功 1号');
return response;
}, function (error) {
console.log('响应拦截器 失败 1号')
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
console.log('响应拦截器 成功 2号')
return response;
}, function (error) {
console.log('响应拦截器 失败 2号')
return Promise.reject(error);
});
//发送请求
axios({
method: 'GET',
url: 'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
</body>
</html>
Ⅳ-请求取消功能模拟实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>取消请求</title>
<link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
<!-- <script src="./node_modules/axios/dist/mine-axios.js"></script> -->
</head>
<body>
<div class="container">
<h2 class="page-header">axios取消请求</h2>
<button class="btn btn-primary"> 发送请求 </button>
<button class="btn btn-warning"> 取消请求 </button>
</div>
<script>
//构造函数
function Axios(config) {
this.config = config;
}
//原型 request 方法
Axios.prototype.request = function (config) {
return dispatchRequest(config);
}
//dispatchRequest 函数
function dispatchRequest(config) {
return xhrAdapter(config);
}
//xhrAdapter
function xhrAdapter(config) {
//发送 AJAX 请求
return new Promise((resolve, reject) => {
//实例化对象
const xhr = new XMLHttpRequest();
//初始化
xhr.open(config.method, config.url);
//发送
xhr.send();
//处理结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
//判断结果
if (xhr.status >= 200 && xhr.status < 300) {
//设置为成功的状态
resolve({
status: xhr.status,
statusText: xhr.statusText
});
} else {
reject(new Error('请求失败'));
}
}
}
//关于取消请求的处理
if (config.cancelToken) {
//对 cancelToken 对象身上的 promise 对象指定成功的回调
config.cancelToken.promise.then(value => {
xhr.abort();
//将整体结果设置为失败
reject(new Error('请求已经被取消'))
});
}
})
}
//创建 axios 函数
const context = new Axios({});
const axios = Axios.prototype.request.bind(context);
//CancelToken 构造函数
function CancelToken(executor) {
//声明一个变量
var resolvePromise;
//为实例对象添加属性
this.promise = new Promise((resolve) => {
//将 resolve 赋值给 resolvePromise
resolvePromise = resolve
});
//调用 executor 函数
executor(function () {
//执行 resolvePromise 函数
resolvePromise();
});
}
//获取按钮 以上为模拟实现的代码
const btns = document.querySelectorAll('button');
//2.声明全局变量
let cancel = null;
//发送请求
btns[0].onclick = function () {
//检测上一次的请求是否已经完成
if (cancel !== null) {
//取消上一次的请求
cancel();
}
//创建 cancelToken 的值
let cancelToken = new CancelToken(function (c) {
cancel = c;
});
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
//1. 添加配置对象的属性
cancelToken: cancelToken
}).then(response => {
console.log(response);
//将 cancel 的值初始化
cancel = null;
})
}
//绑定第二个事件取消请求
btns[1].onclick = function () {
cancel();
}
</script>
</body>
</html>
请求场景遇到的一些问题
- 当请求失败,希望能够自动重试3次以上再失败
安装 axios-retry,可以让你的 Axios 支持自动重试的功能(axios-retry 插件支持配置单个请求)
import Axios, { AxiosRequestConfig } from 'axios'
import axiosRetry from 'axios-retry'
const client = Axios.create({
// 你的配置
})
// 安装 retry 插件
// 当请求失败后,自动重新请求,只有3次失败后才真正失败
axiosRetry(client, { retries: 3 })
export async function request(url: string, config?: AxiosRequestConfig) {
const response = await client.request({ url, ...config })
const result = response.data
// 你的业务判断逻辑
return result
}
// 只有3次失败后才真正失败
const data = request('http://example.com/test')
- 分页数据中,当新的请求发出,自动中断上一次的请求,保持请求唯一
在一个支持翻页的后台表格页,一个用户击翻页按钮,请求发出等待响应,但用户这时点击搜索,需要重新获取数据,支持你的情况可能是:
- 翻页请求先回,搜索数据再回,数据显示正常
- 搜索数据先回,翻页数据再回,数据显示异常(通常在负载均衡的场景中出现)
为此,你希望能够自动取消上一次请求,于是你看了 Axios 的取消请求,但好多地方都需要用到,于是可以将这个功能封装成独立的函数。
import Axios from 'axios'
const CancelToken = Axios.CancelToken
export function withCancelToken(fetcher) {
let abort
function send(data, config) {
cancel() // 主动取消
const cancelToken = new CancelToken(cancel => (abort = cancel))
return fetcher(data, { ...config, cancelToken })
}
function cancel(message = 'abort') {
if (abort) {
abort(message)
abort = null
}
}
return [send, cancel]
}
使用
function getUser(id: string, config?: AxiosRequestConfig) {
return request(`api/user/${id}`, config)
}
// 包装请求函数
const [fetchUser, abortRequest] = withCancelToken(getUser)
// 发送请求
// 如果上一次请求还没回来,会被自动取消
fetchUser('1000')
// 通常不需要主动调用
// 但可以在组件销毁的生命周期中调用
abortRequest()