一、json-server
- 官网文档地址
json-server文档
- 安装json-server
npm install -g json-server
- 目标根目录下创建数据库 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 的理解与使用
1.axios 是什么?
- 前端最流行的 ajax 请求库
- react/vue 官方都推荐使用 axios 发 ajax 请求
- 文档: https://github.com/axios/axios
2.axios 特点
- 基于 xhr + promise 的异步 ajax 请求库
- 浏览器端/node 端都可以使用
- 支持请求/响应拦截器
- 支持请求取消
- 请求/响应数据转换
- 批量发送多个请求
3. 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(): 用来指定接收所有成功数据的回调函数的方法
4.axios 的使用-默认配置
1. 基本使用
<link
crossorigin="anonymous"
href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet"
/>
<div class="container">
<h2 class="page-header">基本使用</h2>
<button class="btn btn-primary">发送GET请求</button>
<button class="btn btn-warning">发送POST请求</button>
<button class="btn btn-success">发送 PUT 请求</button>
<button class="btn btn-danger">发送 DELETE 请求</button>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
const btns = document.querySelectorAll('button')
btns[0].onclick = function () {
axios({
method: 'GET',
url: 'http://localhost:3000/posts/2'
}).then((response) => {
console.log(response)
})
}
btns[1].onclick = function () {
axios({
method: 'POST',
url: 'http://localhost:3000/posts',
data: {
title: '今天天气不错, 还挺风和日丽的',
author: '张三'
}
}).then((response) => {
console.log(response)
})
}
btns[2].onclick = function () {
axios({
method: 'PUT',
url: 'http://localhost:3000/posts/3',
data: {
title: '今天天气不错, 还挺风和日丽的',
author: '李四'
}
}).then((response) => {
console.log(response)
})
}
btns[3].onclick = function () {
axios({
method: 'delete',
url: 'http://localhost:3000/posts/3'
}).then((response) => {
console.log(response)
})
}
</script>
2.默认配置
axios.defaults.method = 'GET';
axios.defaults.baseURL = 'http://localhost:3000';
axios.defaults.params = {id:100};
axios.defaults.timeout = 3000;
btns[0].onclick = function(){
axios({
url: '/posts'
}).then(response => {
console.log(response);
})
}
5. 原理图
6. 难点语法的理解和使用
1、axios.create(config)
-
根据指定配置创建一个新的 axios, 也就就每个新 axios 都有自己的配置 -
新 axios 只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的 -
为什么要设计这个语法?
(1) 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一样, 如何处理
(2) 解决: 创建 2 个新 axios, 每个都有自己特有的配置, 分别应用到不同要 求的接口请求中
const duanzi = axios.create({
baseURL: 'https://api.apiopen.top',
timeout: 2000
});
const onather = axios.create({
baseURL: 'https://b.com',
timeout: 2000
});
duanzi.get('/getJoke').then(response => {
console.log(response.data)
})
2、拦截器函数/ajax 请求/请求的回调函数的调用顺序
- 说明: 调用 axios()并不是立即发送 ajax 请求, 而是需要经历一个较长的流程
- 流程: 请求拦截器2 => 请求拦截器1 => 发ajax请求 => 响应拦截器1 => 响应拦截器 2 => 请求的回调
- 注意: 此流程是通过 promise 串连起来的, 请求拦截器传递的是 config, 响应 拦截器传递的是 response
<script>
axios.interceptors.request.use(function (config) {
console.log('请求拦截器 成功 - 1号');
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.timeout = 2000;
return config;
}, function (error) {
console.log('请求拦截器 失败 - 2号');
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
console.log('响应拦截器 成功 1号');
return response.data;
}, 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、取消请求
- 基本流程 配置 cancelToken 对象
- 缓存用于取消请求的 cancel 函数
- 在后面特定时机调用 cancel 函数取消请求
- 在错误回调中判断如果 error 是 cancel, 做相应处理
- 实现功能 点击按钮, 取消某个正在请求中的请求,
<script>
const btns = document.querySelectorAll('button');
let cancel = null;
btns[0].onclick = function () {
if (cancel !== null) {
cancel();
}
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
cancelToken: new axios.CancelToken(function (c) {
cancel = c;
})
}).then(response => {
console.log(response);
cancel = null;
})
}
btns[1].onclick = function () {cancel(); }
</script>
三、axios 源码与分析
1. Axios的难点问题
1. 目录结构
├── /dist/ # 项目输出目录 ├── /lib/ # 项目源码目录 │ ├── /adapters/ # 定义请求的适配器 xhr、http │ │ ├── http.js # 实现 http 适配器(包装 http 包) │ │ └── xhr.js # 实现 xhr 适配器(包装 xhr 对象) │ ├── /cancel/ # 定义取消功能 │ ├── /core/ # 一些核心功能 │ │ ├── Axios.js # axios 的核心主类 │ │ ├── dispatchRequest.js # 用来调用 http 请求适配器方法发送请求的函数 │ │ ├── InterceptorManager.js # 拦截器的管理器 │ │ └── settle.js # 根据 http 响应状态,改变 Promise 的状态 │ ├── /helpers/ # 一些辅助方法 │ ├── axios.js # 对外暴露接口 │ ├── defaults.js # axios 的默认配置 │ └── utils.js # 公用工具 ├── package.json # 项目信息 ├── index.d.ts # 配置 TypeScript 的声明文件 └── index.js # 入口文件
2. axios 与 Axios 的关系
- 从
语法 上来说: axios 不是 Axios 的实例 - 从
功能 上来说: axios 是 Axios 的实例 - axios 是
Axios.prototype.request 函数 bind()返回的函数 - axios 作为对象有 Axios 原型对象上的所有方法, 有 Axios 对象上所有属性
3. instance 与 axios 的区别?
- 相同:
(1) 都是一个能发任意请求的函数: request(config) (2) 都有发特定请求的各种方法: get()/post()/put()/delete() (3) 都有默认配置和拦截器的属性: defaults/interceptors - 不同:
(1) 默认配置很可能不一样 (2) instance 没有 axios 后面添加的一些方法: create()/CancelToken()/all()
4. axios运行的整体流程
-
整体流程: request(config) ===> dispatchRequest(config) ===> xhrAdapter(config) -
request(config): 将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise -
dispatchRequest(config): 转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数 据. 返回 promise -
xhrAdapter(config): 创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise -
流程图:
5. axios 的请求/响应拦截器是什么?
- 请求拦截器:
Ⅰ- 在真正发送请求前执行的回调函数 Ⅱ- 可以对请求进行检查或配置进行特定处理 Ⅲ- 成功的回调函数, 传递的默认是 config(也必须是) Ⅳ- 失败的回调函数, 传递的默认是 error - 响应拦截器
Ⅰ- 在请求得到响应后执行的回调函数 Ⅱ- 可以对响应数据进行特定处理 Ⅲ- 成功的回调函数, 传递的默认是 response Ⅳ- 失败的回调函数, 传递的默认是 error
6. axios 的请求/响应数据转换器是什么?
- 请求转换器: 对请求头和请求体数据进行特定处理的函数
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
- 响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数
response.data = JSON.parse(response.data)
7. response与error 的整体结构
- response的整体结构
{
data, status,statusText,headers,config,request
}
- error 的整体结构
{
message,response,request,
}
8. 如何取消未完成的请求?
- 当配置了 cancelToken 对象时, 保存 cancel 函数
(1) 创建一个用于将来中断请求的 cancelPromise (2) 并定义了一个用于取消请求的 cancel 函数 (3) 将 cancel 函数传递出来 - 调用 cancel()取消请求
(1) 执行 cacel 函数, 传入错误信息 message (2) 内部会让 cancelPromise 变为成功, 且成功的值为一个 Cancel 对象 (3) 在 cancelPromise 的成功回调中中断请求, 并让发请求的 proimse 失败, 失败的 reason 为 Cancel 对象
2. Axios源码模拟实现
1. axios 的创建过程模拟实现
大概步骤
<script>
function Axios(config) {
this.defaults = config
this.interceptors = {
request: {},
response: {}
}
}
Axios.prototype.request = function (config) {
console.log('发送 AJAX 请求 请求的类型为 ' + config.method)
}
Axios.prototype.get = function (config) {
return this.request({ method: 'GET' })
}
Axios.prototype.post = function (config) {
return this.request({ method: 'POST' })
}
function createInstance(config) {
let context = new Axios(config)
let instance = Axios.prototype.request.bind(context)
Object.keys(Axios.prototype).forEach((key) => {
instance[key] = Axios.prototype[key].bind(context)
})
Object.keys(context).forEach((key) => {
instance[key] = context[key]
})
return instance
}
let axios = createInstance()
axios.get({})
axios.post({})
</script>
2. axios发送请求过程详解
- 整体流程:
request(config) ==> dispatchRequest(config) ==> xhrAdapter(config) - request(config):
将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise - dispatchRequest(config):
转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数 据. 返回 promise - xhrAdapter(config):
创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise
<!--
1. 声明构造函数 Axios ==> request
(1) 创建一个 promise 对象 promise
(2) 声明一个数组 chains
(3) 调用 then 方法指定回调数组 result
2. dispatchRequest 函数
调用适配器发送请求 xhrAdapter ==> then
3. adapter 适配器
返回promise对象,并发送 AJAX(xhr) 请求
xhr open send onreadystatechange readyState status
4. 创建 axios 函数 then 调用 url:'http://localhost:3000/posts'
-->
<script>
function Axios(config) {
this.config = config;
}
Axios.prototype.request = function (config) {
let promise = Promise.resolve(config);
let chains = [dispatchRequest, undefined];
let result = promise.then(chains[0], chains[1]);
return result;
}
function dispatchRequest(config) {
return xhrAdapter(config).then(response => {
return response;
}, error => {
throw error;
});
}
function xhrAdapter(config) {
console.log('xhrAdapter 函数执行');
return new Promise((resolve, reject) => {
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(),
request: xhr,
status: xhr.status,
statusText: xhr.statusText
});
} else {
reject(new Error('请求失败 失败的状态码为' + xhr.status));
}
}
}
});
}
let axios = Axios.prototype.request.bind(null);
axios({
method: 'GET',
url: 'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
3. 拦截器的模拟实现
- array.shift()该方法用于把数组的第一个元素从其中删除,并返回第一个元素的值
- 思路为先将拦截器的响应回调与请求回调都压入一个数组中,之后进行遍历运行
promise = promise.then(chains.shift(), chains.shift()); 通过循环使用promise的then链条得到最终的结果–>等式前面的promise 将被最终的结果覆盖
<!--
1.构造函数 Axios
interceptors ==> new InterceptorManager
2.拦截器管理器构造函数 InterceptorManager handlers
3.发送请求 难点与重点
创建promise对象 创建chains数组 处理拦截器
forEach 遍历 unshift push
while 筛选
4.发送请求 dispatchRequest
5.创建实例 context axios 添加属性
-->
<script>
function Axios(config) {
this.config = config
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
function InterceptorManager() {
this.handlers = []
}
InterceptorManager.prototype.use = function (fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected
})
}
Axios.prototype.request = function (config) {
let promise = Promise.resolve(config)
const chains = [dispatchRequest, undefined]
this.interceptors.request.handlers.forEach((item) => {
chains.unshift(item.fulfilled, item.rejected)
})
this.interceptors.response.handlers.forEach((item) => {
chains.push(item.fulfilled, item.rejected)
})
while (chains.length > 0) {
promise = promise.then(chains.shift(), chains.shift())
}
return promise
}
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
resolve({
status: 200,
statusText: 'OK'
})
})
}
let context = new Axios({})
let axios = Axios.prototype.request.bind(context)
Object.keys(context).forEach((key) => {
axios[key] = context[key]
})
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>
4. 请求取消功能模拟实现
<!--
1.构造函数 Axios
2.原型 request 方法
3.dispatchRequest 函数
4.xhrAdapter函数 --- 发送AJAX请求
是否取消请求 xhr.abort()
5.CancelToken 构造函数
声明变量 实例添加属性 调用 executor 函数
6.创建 axios 函数
-->
<title>取消请求</title>
<link
crossorigin="anonymous"
href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet"
/>
<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
}
Axios.prototype.request = function (config) {
return dispatchRequest(config)
}
function dispatchRequest(config) {
return xhrAdapter(config)
}
function xhrAdapter(config) {
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) {
config.cancelToken.promise.then((value) => {
xhr.abort()
reject(new Error('请求已经被取消'))
})
}
})
}
function CancelToken(executor) {
var resolvePromise
this.promise = new Promise((resolve) => {
resolvePromise = resolve
})
executor(function () {
resolvePromise()
})
}
const context = new Axios({})
const axios = Axios.prototype.request.bind(context)
const btns = document.querySelectorAll('button')
let cancel = null
btns[0].onclick = function () {
if (cancel !== null) {
cancel()
}
let cancelToken = new CancelToken(function (c) {
cancel = c
})
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
cancelToken: cancelToken
}).then((response) => {
console.log(response)
cancel = null
})
}
btns[1].onclick = function () {
cancel()
}
</script>
|