1.1. axios 是什么?
- 前端最流行的 ajax 请求库
- react/vue 官方都推荐使用 axios 发 ajax 请求
- 文档: https://github.com/axios/axios 1.2. axios 特点
- 基于 xhr + promise 的异步 ajax 请求库
- 浏览器端/node 端都可以使用
- 支持请求/响应拦截器
- 支持请求取消
- 请求/响应数据转换
- 批量发送多个请求
如果你没有服务器 则课在本地 创建一个简单的服务,有利于效率提高
前置知识 promise ajax
学习axios之前需要启动服务 如果没有后台接口,直接使用下面方法 创建一个服务
json-serve
安装
npm install -g json-serve
创建一个db.json 文件
开启服务
学习axios
安装方式
项目中使用 npm install axios /yarn add axios
或者使用script标签
通过bootcdn.cn 网站搜索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>
<input type="button" value="发送get请求">
<input type="button" value="发送post请求">
<input type="button" value="发送put请求">
<input type="button" value="发送delete请求">
</body>
<script src="./node_modules/axios/dist/axios.js"></script>
<script>
const btns = document.querySelectorAll("input");
btns[0].onclick = () => {
axios({
// 请求类型
method: 'get',
// URL
url: 'http://localhost:3000/posts',
}).then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
})
}
// 添加数据
btns[1].onclick = () => {
axios({
method: 'post',
url: 'http://localhost:3000/posts',
// 请求体
data: {
title: "发送数据给json-serve",
author: "发送请求"
}
}).then((res) => {
console.log(res)
})
}
// 跟新数据
btns[2].onclick = () => {
axios({
method: 'put',
url: 'http://localhost:3000/posts/3',
// 设置请求体
data: {
title: "我更改了title",
author: "我更改了author"
}
}).then((res) => {
console.log(res)
})
}
// 删除数据
btns[3].onclick = () => {
axios({
method: 'delete',
url: 'http://localhost:3000/posts/3',
}).then((res) => {
console.log(res)
})
}
</script>
</html>
则可进行 对db.json 文件进行修改
请求响应结果
请求配置
这些是创建请求时可以用的配置选项。只有 url 是必需的。如果没有指定 method,请求将默认使用 get 方法。
{
// url 是用于请求的服务器 URL
url: '/user',
// method 是创建请求时使用的方法
method: 'get', // 默认是 get
// baseURL 将自动加在 url 前面,除非 url 是一个绝对 URL。
// 它可以通过设置一个 baseURL 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// transformRequest 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// transformResponse 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// headers 是即将被发送的自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// params 是即将与请求一起发送的 URL 参数// 必须是一个无格式对象(plain object)或 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', 和 'PATCH'
// 在没有设置 transformRequest 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属:FormData, File, Blob
// - Node 专属: Stream
data: {
firstName: 'Fred'
},
// timeout 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 timeout 的时间,请求将被中断
timeout: 1000,
// withCredentials 表示跨域请求时是否需要使用凭证
withCredentials: false, // 默认的
// adapter 允许自定义处理请求,以使测试更轻松
// 返回一个 promise 并应用一个有效的响应 (查阅 response docs).
adapter: function (config) {
/* ... */
},
// auth 表示应该使用 HTTP 基础验证,并提供凭据
// 这将设置一个 Authorization 头,覆写掉现有的任意使用 headers 设置的自定义 Authorization头
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// responseType 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // 默认的
// xsrfCookieName 是用作 xsrf token 的值的cookie的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// xsrfHeaderName 是承载 xsrf token 的值的 HTTP 头的名称
xsrfHeaderName: 'X-XSRF-TOKEN', // 默认的
// onUploadProgress 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
// onDownloadProgress 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
// maxContentLength 定义允许的响应内容的最大尺寸
maxContentLength: 2000,
// validateStatus 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 validateStatus 返回 true (或者设置为 null 或 undefined),promise 将被 resolve; 否则,promise 将被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // 默认的
},
// maxRedirects 定义在 node.js 中 follow 的最大重定向数目
// 如果设置为0,将不会 follow 任何重定向
maxRedirects: 5, // 默认的
// httpAgent 和 httpsAgent 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
// keepAlive 默认没有启用
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// 'proxy' 定义代理服务器的主机名称和端口
// auth 表示 HTTP 基础验证应当用于连接代理,并提供凭据
// 这将会设置一个 Proxy-Authorization 头,覆写掉已有的通过使用 header 设置的自定义 Proxy-Authorization 头。
proxy: {
host: '127.0.0.1',
port: 9000,
auth: : {
username: 'mikeymike',
password: 'rapunz3l'
}
// cancelToken 指定用于取消请求的 cancel token
// (查看后面的 Cancellation 这节了解更多)
cancelToken: new CancelToken(function (cancel) {
})
}
响应结构
某个请求的响应包含以下信息
{
// data 由服务器提供的响应
data: {},
// status 来自服务器响应的 HTTP 状态码
status: 200,
// statusText 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// headers 服务器响应的头
headers: {},
// config 是为请求提供的配置信息
config: {}
}
使用 then 时,你将接收下面这样的响应:
axios.get('/user/12345')
.then(function(response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
在使用 catch 时,或传递 rejection callback 作为 then 的第二个参数时,响应可以通过 error 对象可被使用,正如在错误处理这一节所讲。
配置的默认值/defaults
你可以指定将被用在各个请求的配置默认值
全局的 axios 默认值
axios.defaults.method='GET'
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.params={id:1}
axios.defaults.timeout=300
自定义实例默认值
// 创建实例时设置配置的默认值
var instance = axios.create({
baseURL: 'https://api.example.com'
});
// 在实例已创建后修改默认值
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
创建实例对象发送请求
const duanzi = axios.create({
url: 'https://api.apiopen.top',
})
duanzi({
url: '/getJoke'
}).then(response => {
console.log(response)
})
const onather = axios.create({
baseURL: 'https://www.baiducom',
timeout: 3000,
})
拦截器
分为两类 : 请求拦截器 响应拦截器
请求拦截器:发送请求是 通过一些函数 来判断是否有问题如果有问题,则停止发送请求
响应拦截器:处理结果之前可以先对结果进行预处理
// 修改config中的参数 config 配置对象
axios.interceptors.request.use(config => {
// Do something before request is sent
console.log('请求拦截器 成功 - 1号')
return config;
}, error => {
// Do something with request error
console.log('请求拦截器 失败 - 1号')
return Promise.reject(error);
});
axios.interceptors.request.use(config => {
// Do something before request is sent
console.log('请求拦截器 成功 - 2号')
return config;
}, error => {
// Do something with request error
console.log('请求拦截器 失败 - 2号')
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
// Do something before response is sent
console.log('响应拦截器 成功 - 1号')
return response;
}, error => {
// Do something with response error
console.log('响应拦截器 失败 - 1号')
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
// Do something before response is sent
console.log('响应拦截器 成功 - 2号')
return response;
}, error => {
// Do something with response error
console.log('响应拦截器 失败 - 2号')
return Promise.reject(error);
});
axios({
method: 'post',
url: 'http://localhost:3000/posts',
}).then(response => {
console.log("获取数据")
}).catch(err => {
console.log("数据获取失败")
})
如果你想在稍后移除拦截器,可以这样:
var myInterceptor = axios.interceptors.request.use(function () {/.../});
axios.interceptors.request.eject(myInterceptor);
可以为自定义 axios 实例添加拦截器
var instance = axios.create();
instance.interceptors.request.use(function () {/.../});
请求取消
// 声明区局变量
let cancel = null;
btns[0].onclick = () => {
// 检测上一次请求是否已经完成
if(cancel!=null){
cancel()
}
axios({
method: 'post',
url: 'http://localhost:3000/posts',
// 添加配置对象的属性
cancelToken: new axios.CancelToken((c) => {
// 将c的值赋值给cancel
cancel = c
})
}).then(response => {
console.log(response)
})
}
btns[1].onclick = () => {
cancel()
}
源码分析
├── /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 # 入口文件
模拟ajax请求
// 声明构造函数
function Axios(config) { this.config = config }
// 发送请求
Axios.prototype.request = function (config) {
// 发送请求
// 创建一个proist对象
let promise = Promise.resolve(config);
// 声明一个数组
let chains = [dispatchRequest, undefined]
// 调用then 方法指定回调
let result = promise.then(chains[0], chains[1])
// 返回promise 的结果
return result;
}
// dispatchRequest 函数
function dispatchRequest(config) {
// 调用适配器发送请求
return xhrAdapter(config).then(response => {
// 响应的结果进行转换处理
return response
}, error => {
throw error
})
}
// adapter 适配器
function xhrAdapter(config) {
console.log('xhr')
return new Promise((reslove, 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,
// xhr请求对象
request: xhr,
// 响应状态码
status: xhr.status,
// 响应状态符
statusText: xhr.statusText
})
} else {
reject(new Error('失败状态码', xhr.statusText))
}
}
}
})
}
// 创建axios函数
let axios = Axios.prototype.request.bind(null)
axios({
method: 'post',
url: 'http://localhost:3000/posts',
}).then(response => {
console.log(response)
})
拦截器
// 构造函数
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)
// 创建数组
let chains = [dispatchRequest, undefined]
// 请求拦截器 将请求拦截器的回调 压入到 chains的前面
this.interceptors.request.handlers.forEach(element => {
chains.unshift(element.fulfilled.rejected);
});
// 响应拦截器
this.interceptors.response.handlers.forEach(element => {
chains.push(element.fulfilled, element.rejected)
})
// 遍历
while (chains.length > 0) {
promise = promise.then(chains.shift(), chains.shift())
}
return promise;
}
// 发送请求
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 返回一个promise队形
resolve({
status: 200,
statusText: 'OK'
})
})
}
// 创建实例
let context = new Axios({})
// 创建context属性 config interceptors 添加函数对象身上
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);
});
请求取消
//构造函数
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();
}
拦截器
// 构造函数
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)
// 创建数组
let chains = [dispatchRequest, undefined]
// 请求拦截器 将请求拦截器的回调 压入到 chains的前面
this.interceptors.request.handlers.forEach(element => {
chains.unshift(element.fulfilled.rejected);
});
// 响应拦截器
this.interceptors.response.handlers.forEach(element => {
chains.push(element.fulfilled, element.rejected)
})
// 遍历
while (chains.length > 0) {
promise = promise.then(chains.shift(), chains.shift())
}
return promise;
}
// 发送请求
function dispatchRequest(config) {
return new Promise((resolve, reject) => {
// 返回一个promise队形
resolve({
status: 200,
statusText: 'OK'
})
})
}
// 创建实例
let context = new Axios({})
// 创建axios
let axios = Axios.prototype.request.bind(context)
// 创建context属性 config interceptors 添加函数对象身上
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/1'
}).then(response => {
console.log(response);
});
请求取消
let btns = document.querySelectorAll("input")
// 构造函数
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.status === 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)
function CancelToken(executor) {
var resolvePromise;
this.promise = new Promise((resolve) => {
resolvePromise = resolve;
})
executor(function () {
resolvePromise()
})
}
let cancel = null;
btns[0].onclick = function () {
if (cancel !== null) {
cancel()
}
let cancelToken = new CancelToken(function (c) {
cancel = c
})
axios({
method: 'post',
url: 'http://localhost:3000/post',
}).then(respinse => {
console.log(response)
cancel = null
})
}
btns[1].onclick = () => {
cancel();
}