写在前面
- 本文主要是介绍前端开发过程中对于Axios的基础使用以及并且了解其axios的源码实现后手写部分axios内置api方法加深对其理解;
- 阅读本文你能够了解到为什么在使用Axios发送请求时:
- 我们能够通过函数调用的方式【Axios(config)】又或是函数对象的方式【Axios.xxx(config)】指定请求方式便捷的发起请求;
- 请求拦截器和响应拦截器的执行顺序原理;
- axios是如何实现取消请求;
- 在使用Axios之前,需要提前了解es6中Promise以及XMLHttpRequest。
一、Axios是什么?有什么特点?
- Axios是一个基于Promise的网络请求库,作用于nodejs和浏览器中;在服务端使用原生node中http模块发送请求,浏览器中使用XMLHttpRequest请求;
- 特点:
- 1.拦截请求和响应;2.转换请求和响应数据;3.取消请求;4.自动转换json数据;5.客户端支持XSRF(跨站请求伪造)
二、简单服务搭建以及axios的基本使用
1使用json-server搭建简单服务器
- 使用它创建一些简单的数据,以及模拟后端接口返回
npm install -g json-server
- 创建一个文件夹(这里叫server),初始化package.json文件
npm init -y
- 在该文件夹下创建一个json文件,里边写点假数据用于后续测试
{
"title": [
{
"id": 1,
"title": "这是标题",
"author": "polaris"
}
],
"desc": [
{
"id": 1,
"body": "北极星超级帅。。。",
"postId": 1
}
],
"intro": {
"name": "hahhaaa"
}
}
- 启动服务,npx(Node 程序包运行器)
npx json-server data.json
- 执行之后控制台可以看到,如下图,左边附上目录结构;浏览器输入地址就能看到输出了。
- 这里我们可以吧启动命令在package.json中配置一下,不然每次敲一行有点麻烦,也不符合我们的开发习惯;所以我们打开package.json文件,在scripts对象中新增执行命令;
- --watch简写(-w)标识监听data.json的改变,在文件内容发送变化后自动执行服务,保证接口返回的是最新的数据;
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
//新增此行
"serve": "json-server --watch data.json",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
- 现在我们启动服务就只需要使用命令:
npm run serve
- 以上步骤我们搭建了一个简单的服务,现在开始引入axios进行基本的使用
2使用axios
- 与server文件夹平级创建src目录,新建index.html文件
- 在index.html中使用script链接的方式引入axios进行使用,链接点这里
- 浏览器中查看
- 除了get方法外还有post,put等方法,可以自行尝试;
3 配置补充
3.1 请求别名方式调用
- 上面请求写法可以看到,我们可以使用axios(config),config为传入的配置对象或是axios.get这样的方式直接指定该请求的类型;
- 更多:
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]])
3.2 请求配置
- 也就是axios(config)中的config配置对象都有哪些可以自定义的属性:
{
// `url` 是用于请求的服务器 URL
url: '/user',
// `method` 是创建请求时使用的方法
method: 'get', // 默认值
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
// 你可以修改请求头。
transformRequest: [function (data, headers) {
// 对发送的 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对接收的 data 进行任意转换处理
return data;
}],
// 自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是与请求一起发送的 URL 参数
// 必须是一个简单对象或 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', 'DELETE 和 'PATCH' 请求方法
// 在没有设置 `transformRequest` 时,则必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属: FormData, File, Blob
// - Node 专属: Stream, Buffer
data: {
firstName: 'Fred'
},
// 发送请求体数据的可选语法
// 请求方式 post
// 只有 value 会被发送,key 则不会
data: 'Country=Brasil&City=Belo Horizonte',
// `timeout` 指定请求超时的毫秒数。
// 如果请求时间超过 `timeout` 的值,则请求会被中断
timeout: 1000, // 默认值是 `0` (永不超时)
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,这使测试更加容易。
// 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
adapter: function (config) {
/* ... */
},
// `auth` HTTP Basic Auth
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示浏览器将要响应的数据类型
// 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
// 浏览器专属:'blob'
responseType: 'json', // 默认值
// `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
// 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // 默认值
// `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
xsrfCookieName: 'XSRF-TOKEN', // 默认值
// `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
// `onUploadProgress` 允许为上传处理进度事件
// 浏览器专属
onUploadProgress: function (progressEvent) {
// 处理原生进度事件
},
// `onDownloadProgress` 允许为下载处理进度事件
// 浏览器专属
onDownloadProgress: function (progressEvent) {
// 处理原生进度事件
},
// `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
maxContentLength: 2000,
// `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
maxBodyLength: 2000,
// `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
// 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
// 则promise 将会 resolved,否则是 rejected。
validateStatus: function (status) {
return status >= 200 && status < 300; // 默认值
},
// `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
// 如果设置为0,则不会进行重定向
maxRedirects: 5, // 默认值
// `socketPath` 定义了在node.js中使用的UNIX套接字。
// e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
// 只能指定 `socketPath` 或 `proxy` 。
// 若都指定,这使用 `socketPath` 。
socketPath: null, // default
// `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
// and https requests, respectively, in node.js. This allows options to be added like
// `keepAlive` that are not enabled by default.
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// `proxy` 定义了代理服务器的主机名,端口和协议。
// 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
// 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
// `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
// 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
// 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// see https://axios-http.com/zh/docs/cancellation
cancelToken: new CancelToken(function (cancel) {
}),
// `decompress` indicates whether or not the response body should be decompressed
// automatically. If set to `true` will also remove the 'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
decompress: true // 默认值
}
3.3 响应结构
- 这就是发起请求之后返回的响应对象结构
{
// `data` 由服务器提供的响应,会自动解析为json格式
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 是服务器响应头
// 所有的 header 名称都是小写,而且可以使用方括号语法访问
// 例如: `response.headers['content-type']`
headers: {},
// `config` 是 `axios` 请求的配置信息
config: {},
// `request` 是生成此响应的请求
// 在node.js中它是最后一个ClientRequest实例 (in redirects),
// 在浏览器中则是 XMLHttpRequest 实例
request: {}
}
3.4 默认的全局配置
- 例如get请求方式,这是默认的请求类型,所以下面的method属性为get时可以省略
axios({
method: 'get', //默认值
url: 'http://localhost:3000/title',
}).then((res) => {
console.log(res)
})
- 其余默认配置
//请求基础地址,比如设置为如下,那么我们在配置url属性的时候就只需要写基础地址之后的内容
//url: 'http://localhost:3000/title' => url: /title
axios.defaults.baseURL = 'https://localhost:3000';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
//Content-Type来表示具体请求中的媒体类型信息,确切的来说是客户端告知服务端,
//自己即将发送的请求消息携带的数据结构类型,好让服务端接收后以合适的方式处理。
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
//请求类型
axios.defaults.method = 'get';
3.5 自定义默认实例
- 通过axios.create方法创建自定义实例,每个实例都能够独立的配置,比如默认域名地址等等
// 创建实例时配置默认值
const instance = axios.create({
baseURL: 'https://localhost:3000'
});
const instance = axios.create({
baseURL: 'https://localhost:9000'
});
// 创建实例后修改默认值
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
//使用起来
instance({
method: 'get',
url: '/title/1'
}).then(res => {
console.log(res)
})
3.6 拦截器
- 请求与响应拦截器分别有成功与失败的回调函数
- 当请求拦截到请求失败的时候,对应的响应拦截器执行的就是响应失败的回调
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
3.7 取消请求
- 为了测试请求取消,需要设置package.json中的服务命令,新增2s延时方便测试
{
//...
"scripts": {
"serve": "json-server --watch data.json -d 2000"
}
}
- 测试例子,点击发送请求后在2s内点击取消请求;
<!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>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script>
</head>
<body>
<button>发起请求</button>
<button>quxiao 请求 post</button>
<script>
const CancelToken = axios.CancelToken
let btn = document.querySelectorAll('button')
let cancel = null
btn[0].onclick = function () {
if (cancel !== null) return
axios({
url: 'http://localhost:3000/title',
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c
}),
}).then((res) => {
console.log(res)
cancel = null
})
}
btn[1].onclick = function () {
cancel()
}
</script>
</body>
</html>
4 简单总结
- 以上内容我们简单搭建了一个服务,并且通过script引入axios测试了一些例子;
- 接下来我们根据axios库模拟实现一些它的api;
三、源码理解以及手写部分api
1 核心目录文件
- 安装axios依赖,这样能够在node_modules目录中查看axios的源码文件
npm i axios
- 打开lib文件夹
1.1 adapters
- 定义请求的适配器 xhr, http
- http.js用于在node服务中向远端发送http请求
- xhr.js用于前端发送ajax请求
1.2 cancel
- 取消事件相关的文件
- CancelToken.js 存放这取消请求的构造函数,使用起来 new axios.CancelToken()
1.3 core目录
- 核心文件
- Axios构造函数
2 实现axios的创建过程
2.1 基本认知
- axios在使用时,既可以当做函数调用也可以通过对象的方式使用,形如: axios(config),axios.get(config);axios支持我们自定义默认配置(例如设置默认请求方式method、基础路径baseUrl等等l)以及请求响应拦截
- 除了上面说的axios的两种方式发送请求,还能通过const instance = axios.create(config) 的方式创建axios实例,使用起来 instance(config),instance.get(config) 与axios全局对象完全一致;既然使用方式相同区别又在哪呢?
- 首先这两种方式本质内部都是调用createInstance函数生成axios实例;
- 不同点为使用axios.create可以创建多个自定义实例instance,我们可以为每个instance进行单独的配置,例如baseUrl,get等等,它是多例模式;而全局的axios只有一个,是单例模式;
- 并且axios身上还具备CanceledError、CancelToken等属性方法,而通过axios.create方法创建出的axios实例则是没有这些属性方法。
2.2 具体实现
2.2.1 Axios构造函数
- 这里我们先初始化一个Axios构造函数,并向器原型对象添加request,get等方法
- 其中request方法用于发送ajax请求,get,post等请求本质都是调用此方法发送请求
// Axios构造函数
function Axios(config) {
// 接收默认属性,例如将请求方法默认设置为get等
this.defaults = config
// 拦截器
this.interceptors = {
//请求拦截器
request: {},
//响应拦截器
response: {},
}
}
// 向Axios原型添加请求方法,其他方法发送请求都是内部调用此方法
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' })
}
2.2.2 createInstance函数
- createInstance函数,用于创建axios实例(这就是暴露给页面使用的axios实例对象)
- 内部主要逻辑是:
- 实例化一个Axios对象(context),此对象包含Axios函数原型的所有方法和属性,以及defaults和interceptors属性
- 通过调用bind方法创建一个request函数的拷贝,绑定函数调用时的this指向为context;将此函数赋值给instance变量,即instance成为一个与request功能相当的函数
- 遍历Axios的原型对象,将它身上的方法复制一份到instance对象身上,这样一来,instance函数就不仅可以通过函数的方式调用,同时也支持对象方法的形式调用;形如 instance(config),instance.get(config)
- 最后,从Axios实例对象context上复制defaults,interceptors属性到instance对象上,至此,instance具备了完整的功能
// 用于创建Axios实例对象
function createInstance(config) {
// 实例化一个对象,该对象继承Axios所有属性方法
const context = new Axios(config)
// 通过bind方法返回一个request函数,将this绑定为Axios实例对象(context)
// 即instance是一个具备request功能的函数了
const instance = Axios.prototype.request.bind(context)
// 将Axios原型对象(prototype)的方法添加到instance函数上
// 这样instance函数对象能够通过instance.xxx的方式调用请求
Object.keys(Axios.prototype).forEach(key => {
// 并且函数绑定context对象,保证this指向正确
instance[key] = Axios.prototype[key].bind(context)
})
// 为instance对象添加 defaults,interceptors属性
Object.keys(context).forEach(key => {
instance[key] = context[key]
})
// 给instance对象添加create属性,也就是create方法,该方法内部调用createInstance方法
// 返回axios实例对象;这样页面能够以 axios.create 方法返回一个axios实例
instance.create = function create(otherConfig) {
//TODO 关于对象合并的操作没有处理完,这里简单介绍一下,所以使用Object.assign
return createInstance(Object.assign(config || {}, otherConfig || {}))
}
//返回这个函数
return instance
}
2.2.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>Document</title>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script> -->
</head>
<body>
<script>
// Axios构造函数
function Axios(config) {
// 默认属性,例如将请求方法默认设置为get等
this.defaults = config
// 拦截器
this.interceptors = {
request: {},
response: {},
}
}
// 向Axios原型添加请求方法,其他方法发送请求都是内部调用此方法
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' })
}
// 用于创建Axios实例对象
function createInstance(config) {
// 实例化一个对象,该对象继承Axios所有属性方法
const context = new Axios(config)
// 通过bind方法返回一个request函数,将this绑定为Axios实例对象(context)
// 即instance是一个具备request功能的函数了
const instance = Axios.prototype.request.bind(context)
// 将Axios原型对象(prototype)的方法添加到instance函数上
// 这样instance函数对象能够通过instance.xxx的方式调用请求
Object.keys(Axios.prototype).forEach((key) => {
// 并且函数绑定context对象,保证this指向正确
instance[key] = Axios.prototype[key].bind(context)
})
// 为instance对象添加 defaults,interceptors属性
Object.keys(context).forEach((key) => {
instance[key] = context[key]
})
return instance
}
// 测试
const axios = createInstance()
axios({method: 'get'})
axios.get()
</script>
</body>
</html>
3 实现axios发送请求
3.1 基本流程
- 使用axios(config)发送get请求为例子
- axios内部请求最终是调用request方法发送请求,该方法会返回一个promsie对象
- request函数内部首先创建一个成功的promsie实例,并且指定成功的回调函数为dispatchRequest,失败的回调为undefined;其中dispatchRequest函数内部调用xhrAdapter方法,该方法内部通过XMLHttpRequest向后端服务发送请求,并将结果存入到promsie实例当中返回
- 即dispatchRequest函数内部调用xhrAdapter方法,由xhrAdapter发起请求后将结果返回给dispatchRequest函数,最终request函数中promsie指定的成功回调拿到dispatchRequest函数返回的结果
3.2 逻辑实现
<!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>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script> -->
</head>
<body>
<script>
// Axios构造函数
function Axios(config) {
// 默认属性,例如将请求方法默认设置为get等
this.defaults = config
}
// 向Axios原型添加请求方法,其他方法发送请求都是内部调用此方法
Axios.prototype.request = function (config) {
// 创建一个成功的promsie
const promise = Promise.resolve(config)
// 声明数组,存入请求回调和一个undefined(用于占位)
const chains = [dispatchRequest, undefined]
// // 将dispatchRequest函数作为promise的成功回调
const result = promise.then(chains[0], chains[1])
// // 返回promise结果值
return result
// console.log(promise)
}
// dispatchRequest函数,这里调用xhrAdapter函数发送请求
function dispatchRequest(config) {
// 调用xhr适配器发送请求
return xhrAdapter(config).then(
(res) => {
// todo 对响应结果进行转换处理
return res
},
(err) => {
return err
}
)
}
// xhr adapter适配器函数,这里用于发送ajax请求
function xhrAdapter(config) {
return new Promise((reslove, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(config.method, config.url)
xhr.send(null)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
reslove({
config: config, //配置对象
data: xhr.response, //响应体
headers: xhr.getAllResponseHeaders(), //字符串
request: xhr, //xhr请求对象
status: xhr.status, //响应状态码
statusText: xhr.statusText, //响应状态字符串
})
} else {
reject(new Error('失败了:', xhr.status))
}
}
}
})
}
// 创建axios函数
const axios = Axios.prototype.request.bind(null)
axios({ method: 'GET', url: 'http://localhost:3000/title' }).then(
(res) => {
console.log('res', res)
}
)
// axios.get()
</script>
</body>
</html>
4 实现axios拦截器的功能
- 拦截器构造函数为InterceptorManager,并且interceptors对象中的request、response属性值均为InterceptorManager实例对象
- InterceptorManager函数实例上存在handlers数组,用于存储拦截器的回调函数;函数原型上存在use方法,该方法用于将请求、响应拦截的回调存入handlers
4.1 代码实现以及测试例子
- 这个例子能够说明为什么多个定义了多个拦截器时,拦截器的执行顺序总是先请求拦截器再执行响应拦截器
<!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>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script> -->
</head>
<body>
<script>
function Axios(config) {
this.config = config
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
// 拦截器构造函数
function InterceptorManager() {
this.handlers = []
}
// use方法,用于存储拦截器的回调函数
InterceptorManager.prototype.use = function(fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected
})
}
Axios.prototype.request = function(config) {
// 创建promsie对象
let promise = Promise.resolve(config)
// 创建一个数组,放入请求函数和undefined占位,因为执行过程是每次取两个函数
let chains = [dispatchRequest, undefined]
// 处理拦截器
// 请求拦截器,循环遍历请求拦截器的回调,添加到chains数组头部
this.interceptors.request.handlers.forEach(item => {
chains.unshift(item.fulfilled, item.rejected)
})
// 响应拦截器,循环遍历请求拦截器的回调,添加到chains数组末尾
this.interceptors.response.handlers.forEach(item => {
chains.push(item.fulfilled, item.rejected)
})
// console.log(chains)
// 遍历执行chains中的回调,从数组顶部依次取出执行
while(chains.length > 0) {
// 每次取出两个回调,分别指定为成功,失败
// 当成功执行后会返回一个新的promise,接着执行
promise = promise.then(chains.shift(), chains.shift())
}
return promise
}
// 简单默认发送请求
function dispatchRequest() {
return new Promise((reslove, reject) => {
reslove({
status: 200,
statusText: 'ok'
})
})
}
const context = new Axios({})
const axios = Axios.prototype.request.bind(context)
Object.keys(context).forEach(key => {
axios[key] = context[key]
})
// 添加请求拦截器,测试例子
// use执行时将回调函数存入handers数组当中
axios.interceptors.request.use(
function one(config) {
console.log('请求拦截器成功--111')
return config
},
function one(error) {
console.log('请求拦截器失败-111')
return Promise.reject(error)
}
)
// 添加响应拦截器
axios.interceptors.response.use(
function one(response) {
console.log('响应拦截器成功--111')
// 2xx 范围内的状态码都会触发该函数。
return response
},
function one(error) {
console.log('响应拦截器失败--111')
// 超出 2xx 范围的状态码都会触发该函数。
return Promise.reject(error)
}
)
// 添加请求拦截器
axios.interceptors.request.use(
function two(config) {
console.log('请求拦截器成功--222')
return config
},
function two(error) {
console.log('请求拦截器失败-222')
return Promise.reject(error)
}
)
// 添加响应拦截器
axios.interceptors.response.use(
function two(response) {
console.log('响应拦截器成功--222')
// 2xx 范围内的状态码都会触发该函数。
return response
},
function two(error) {
console.log('响应拦截器失败--222')
// 超出 2xx 范围的状态码都会触发该函数。
return Promise.reject(error)
}
)
console.dir(axios)
axios({
method: 'get',
url: 'http://localhost:3000/title'
}).then(res => {
console.log('res...', res)
})
</script>
</body>
</html>
5 实现axios 取消请求功能
- 主要是靠CancelToken构造函数实现,在实例化CancelToken对象时,需要传入一个带形参的回调函数,这个形参用来接收CancelToken函数内部promise的成功回调
- CancelToken函数实例化时会创建一个临时变量reslovePromise(用于存储回调函数);实例对象上会生成一个属性(这里指promise),并且实例化一个promsie对象赋值给它,这个promise对象的成功回调赋值给临时变量reslovePromise;最后会调用传入的回调函数,并将reslovePromise通过函数的方式暴露给外部;到这里实际就是把promise的状态变更权利交给了外部函数,当执行这个函数时promise状态变更为fulfilled,进而在发送ajax请求(xhrAdapter方法内)时通过状态变化来终止请求xhr.abort
<!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>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script> -->
</head>
<body>
<button>发起请求</button>
<button>取消请求</button>
<script>
function Axios(config) {
this.config = config
}
Axios.prototype.request = function (config) {
return dispatchRequest(config)
}
function dispatchRequest(config) {
return xhrAdapter(config)
}
// 发送ajax请求
function xhrAdapter(config) {
return new Promise((reslove, 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) {
reslove({
status: xhr.status,
statusText: xhr.statusText,
})
} else {
reject(new Error('请求失败!'))
}
}
}
// 取消请求判断
if (config.cancelToken) {
// 状态改变执行成功回调,那么取消请求
config.cancelToken.promise.then((reslove) => {
xhr.abort()
})
}
})
}
const context = new Axios({})
const axios = Axios.prototype.request.bind(context)
// CancelToken构造函数
function CancelToken(executor) {
// 声明变量,用于存储reslove暴露给外部
let reslovePromise
// 给CancelToken实例对象添加一个promise属性,值为promise对象
// 将成功的回调(reslove)赋值给reslovePromise变量
this.promise = new Promise((reslove) => {
reslovePromise = reslove
})
// 调用外部传入的函数
executor(function () {
// 函数内部执行promise成功的回调,改变promise状态为成功
reslovePromise()
})
}
// console.dir(axios)
const btns = document.querySelectorAll('button')
// 临时变量,接收promise的成功回调
let cancel = null
btns[0].onclick = function () {
if (cancel !== null) return
// 实例化一个CancelToken对象,传入一个回调函数
// 构造函数内部会执行这个回调,并且把promise成功回调通过形参c赋值给cancel
// 外部通过调用cancel函数执行reslove函数从而改变promise状态
const cancelToken = new CancelToken(function (c) {
cancel = c
})
axios({
method: 'get',
url: 'http://localhost:3000/title',
cancelToken: cancelToken,
}).then((res) => {
console.log(res)
cancel = null
})
}
btns[1].onclick = function() {
cancel()
}
</script>
</body>
</html>