共计 32595 个字符,预计需要花费 82 分钟才能阅读完成。
- 下图为 2020/01/05 温习时从新绘制
- 下图为 2020/01/05 温习时从新绘制
- 下图为 2021/02/07 再温习申请流程
导航
[[深刻 01] 执行上下文](https://juejin.im/post/684490…)
[[深刻 02] 原型链](https://juejin.im/post/684490…)
[[深刻 03] 继承](https://juejin.im/post/684490…)
[[深刻 04] 事件循环](https://juejin.im/post/684490…)
[[深刻 05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490…)
[[深刻 06] 隐式转换 和 运算符](https://juejin.im/post/684490…)
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…)
[[深刻 08] 前端平安](https://juejin.im/post/684490…)
[[深刻 09] 深浅拷贝](https://juejin.im/post/684490…)
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…)
[[深刻 11] 前端路由](https://juejin.im/post/684490…)
[[深刻 12] 前端模块化](https://juejin.im/post/684490…)
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490…)
[[深刻 14] canvas](https://juejin.im/post/684490…)
[[深刻 15] webSocket](https://juejin.im/post/684490…)
[[深刻 16] webpack](https://juejin.im/post/684490…)
[[深刻 17] http 和 https](https://juejin.im/post/684490…)
[[深刻 18] CSS-interview](https://juejin.im/post/684490…)
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…)
[[深刻 20] 手写函数](https://juejin.im/post/684490…)
[[react] Hooks](https://juejin.im/post/684490…)
[[部署 01] Nginx](https://juejin.im/post/684490…)
[[部署 02] Docker 部署 vue 我的项目](https://juejin.im/post/684490…)
[[部署 03] gitlab-CI](https://juejin.im/post/684490…)
[[源码 -webpack01- 前置常识] AST 形象语法树](https://juejin.im/post/684490…)
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…)
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程](https://juejin.im/post/684490…)
[[源码] Redux React-Redux01](https://juejin.im/post/684490…)
[[源码] axios ](https://juejin.im/post/684490…)
[[源码] vuex ](https://juejin.im/post/684490…)
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…)
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…)
[[源码 -vue03] watch 侦听属性 – 初始化和更新 ](https://juejin.im/post/684490…)
[[源码 -vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490…)
[[源码 -vue05] Vue.extend ](https://juejin.im/post/684490…)
[[源码 -vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790…)
前置常识
一些单词
Interceptors:拦截器
bother:懊恼
ties:系,捆,领带,分割
immutably:不变的
precedence:优先的
determine:确定,查明
(Determine if a value is a FormData 确定值是否是 FormDate 类型)
inherit:继承
<font color=DarkOrchid>axios 勾销申请办法 1 – axios.CancelToken.source()</font>
-
const source = axios.CancelToken.source() 工厂函数
- source.token
- source.cancel(message)
source = {token: cancelToken 对象, cancel: 勾销函数}
- axios.isCancel
-
留神
-
每次申请的 source 不能是同一个 source 对象,如果是同一个 source 对象,就会呈现勾销申请后,不能再次发送申请的状况
(1) 勾销申请后,不能再次发送申请 const CancelToken = axios.CancelToken; const source = CancelToken.source(); // ----------------------------- source 工厂函数 axios.get('/user/12345', { cancelToken: source.token // ------------------------------ token [2021/01/04 更新] // ------------------------------ token,这里勾销申请后,就不能再次发送申请了,因为是同一个 source 对象,即同一个 token // ------------------------------ 解决办法就是把 source 的生成办法每个申请办法中 }).catch(function (thrown) {if (axios.isCancel(thrown)) {console.log('Request canceled', thrown.message); } else {// handle error} }); // 勾销申请 source.cancel('Operation canceled by the user.'); // ----------------- cancel 函数
2021/01/04 温习时发现下面的申请,勾销申请后不能再次发送申请,用如下办法解决
(2) 勾销申请后,能够再次发送申请 import React, {useState} from 'react' import axios from 'axios' const AxiosCancelToken = () => {const [cancalFn1, setCancalFn1] = useState(() => {}) as any const [links] = useState([ { name: 'axios 源码 - axios 源码剖析仓库', url: 'https://github.com/woow-wu7/7-react-admin-ts/tree/master/src/SOURCE-CODE-ANALYSIS/AXIOS' }, { name: 'axios 源码 - 我的掘金博客', url: 'https://juejin.cn/post/6844904147532120072' }, ]) const renderLinks = () => links.map(({ name, url}) => <div key={name}><a href={url} target="blank">{name}</a></div>) // axios 勾销申请 // 办法一 // axios.CancelToken.source.token // axios.CancelToken.source.cancel // 留神点:设个 source 不能是同一个 source,如果是同一个 source 的话,勾销申请后就不能再次从新申请了 const handleRequest = async () => {const source = axios.CancelToken.source() setCancalFn1(() => source.cancel) await new Promise(resolve => {setTimeout(() => {console.log('延时 2s 执行') return resolve('success') }, 2000) }) await axios({ url: '/API/pic.php', method: 'get', cancelToken: source.token }).catch(err => {if (axios.isCancel(err)) {console.log('object :>>', err.message); } else {console.log('error') } }) } const cancelRequest = () => {cancalFn1('申请勾销了') } return ( <div className="axios-cancel-token"> <p>Axios - CancelToken 测试 </p><br /> <div> 请关上浏览器调试面板调式 </div> <button onClick={handleRequest}> 点击发送申请 1 - 每次申请都用 promise 延时 2s 模仿 </button><br /> <button onClick={cancelRequest}> 点击 - 勾销申请办法 1 - 工厂函数 source</button><br /> <div> {renderLinks()} </div> </div> ) } export default AxiosCancelToken
-
<font color=DarkOrchid>axios 勾销申请办法 2 – new axios.CancelToken()</font>
-
const cancalTokenInstance = new axios.CancelToken(c => cancel = c)
- 生成的是 cancalToken 实例
- 参数是一个回调函数,回调函数的参数就是 cancel() 函数
留神传入 CancelToken(c => cancel = c)是一个 cancel 函数,这个 cancel 函数将本人的参数 c 赋值给了 cancel 变量,c 也是一个函数,即 cancel 函数
const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', {cancelToken: new CancelToken(function executor(c) { // An executor function receives a cancel function as a parameter cancel = c; }) }); // cancel the request cancel();
2020/01/06 温习第二种勾销申请的例子,这外面有一个坑,请看上面代码的正文
- 1. 通过间接 new axios.CancelToken()
-
2. 而不是调用 CancelToken 的静态方法 source()生成{token, cancel}
const handleRequest2 = async () => {const token = new axios.CancelToken(c => setCancelFn2(() => c)) // 将 c 赋值给 state await new Promise(resolve => {setTimeout(() => {console.log('延时 2s 执行') return resolve('success') }, 2000) }) await axios({ url: '/API/pic.php', method: 'get', cancelToken: token, // cancelToken: new axios.CancelToken(c => setCancelFn2(() => c)) // 这种写法是不能够的!!!!!!!!!!!!!!!!!!!! }).catch(err => {if (axios.isCancel(err)) {console.log('object :>>', err.message); } else {console.log('error') } }) } const cancelRequest2 = () => {console.log(cancelFn2); cancelFn2('申请勾销了') // state }
<font color=DarkOrchid>axios 勾销申请 – 实例 1 </font>
<template>
<div class="cancal-request">
<div>cancel-request</div>
<div @click="getData"> 申请:点击获取申请数据 - new CancelToken()办法 </div>
<div @click="cancalRequest"> 勾销申请:点击勾销获取申请数据 </div>
<div @click="getData2"> 申请:点击获取申请数据 - source()工厂函数形式 </div>
<div @click="cancalRequest2"> 勾销申请:点击勾销获取申请数据 </div>
<div>{{data.responseTime}}</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "CancelRequest",
data() {
return {
cancelFn: null,
data: {responseTime: null},
source: null
};
},
methods: {getData() {
axios({
baseURL: "/api",
timeout: 2000,
url: "/cancel-request",
method: "get",
cancelToken: new axios.CancelToken(c => {// new axios.CancelToken()
// 返回值 => 是一个 cancelToken
// 参数 => 是一个回调函数,回调函数的参数是 cancel() 函数
this.cancelFn = c;
})
}).then(res => {console.log(res, "res-data");
this.data = res.data
});
},
cancalRequest() {if (this.cancelFn) {this.cancelFn('以 new axios.CancelToken(c => this.cancelFn = c) 形式勾销申请');
// 勾销申请
}
},
getData2() {this.source = axios.CancelToken.source();
// 利用 axios.CancelToken.source() 工厂函数生成一个对象
// source = {token: cancelToken 对象, cancel: 勾销函数}
// source.token
// source.cancel
axios({
baseURL: "/api",
timeout: 2000,
url: "/cancel-request",
method: "get",
cancelToken: this.source.token // token
}).then(res => {console.log(res, "res-data");
this.data = res.data
});
},
cancalRequest2() {if (this.source.cancel) {this.source.cancel('以 source()工厂函数办法 勾销申请'); // cancel
}
}
}
};
</script>
<font color=DarkOrchid>axios 勾销申请 – 实例 2 – mixin 形式 </font>
mixin/index.js
import axios from 'axios';
export const mixinCancelToken = {data() {
return {
cancelToken: null, // ----------------------------------- cancelToken
cancel: null // ----------------------------------------- cacel 勾销函数
}
},
created() {
this.cancelToken = new axios.CancelToken(c => { // -------- cancelToken 赋值
this.cancel = c // -------------------------------------- cancel 函数赋值
})
},
beforeDestroy() {if (this.cancel) {this.cancel(` 勾销申请 mixin 形式 `) // --------------------- 组件卸载时,勾销申请
}
}
}
业务组件中
<template>
<div class="cancal-request-component">
<div>cancel-request-component</div>
<div @click="getData"> 申请:点击获取申请数据 - mixin 形式 </div>
<div @click="cancalRequest"> 勾销申请:点击勾销获取申请数据 - - mixin 形式 </div>
</div>
</template>
<script>
import {mixinCancelToken} from "../mixin";
import axios from "axios";
export default {
name: "CancelRequestComponent",
mixins: [mixinCancelToken], // ----------------------------------------- 注入 mixin
data() {return {};
},
mounted() {},
methods: {getData() {
axios({
baseURL: "/api",
timeout: 2000,
url: "/cancel-request",
method: "get",
cancelToken: this.cancelToken // --------------------------------- 从 mixin 中获取 cancelToken
}).then(res => {console.log(res, "res-data");
this.data = res.data
});
},
cancalRequest() {if (this.cancel) {this.cancel('勾销申请 mixin 形式') // ------------------------------- 从 mixin 中获取 cancel
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="stylus">
.cancal-request-component {background: yellow;}
</style>
axios 拦截器的应用
import axios from 'axios'
const baseURL = '/api'
const headers = {'Content-Type': 'application/json'}
const axiosInstance = axios.create({
baseURL,
headers,
timeout: 1000,
})
console.log(typeof axiosInstance, 'axios.create({...})返回一个函数')
// 申请拦挡
// 1. Axios()构造函数中存在 this.interceptors 对象
// 2. interceptors 对象上有两个属性
// request 属性,是一个实例对象,继承了原型对象上的 use eject forEach 办法等
// response 属性,是一个实例对象,继承了原型对象上的 use eject forEach 办法等
axiosInstance.interceptors.request.use(config => {return config}, error => {return Promise.reject(error)
})
// 响应拦挡
axiosInstance.interceptors.response.use(response => {return response}, error => {return Promise.reject(error)
})
export const baseApi = async ({url, method, paramsOrData}) => {
try {const paramsOrDataKey = method.match(/get/i) ? 'params' : 'data';
const res = await axios.request({
url,
method,
[paramsOrDataKey]: paramsOrData
})
return res
} catch (err) {throw new Error(err)
}
}
export default axiosInstance
XMLHttpRequest
-
如何获取 response???
- xhr.response
- xhr.responseText
- xhr.responseXML
-
xhr.responseText
- 在 xhr.responseType = ‘text’,”,不设置时,xhr 实例对象上才有此属性,此时能力调用
-
xhr.response
- 在 xhr.responseType = ‘text’,” 时,值是 (”)
- 在 xhr.resposneType 是其余值时,值是 (null)
-
xhr.responseType
- text
- document
- json
- blob
- arrayBuffer
const xhr = new XMLHttpRequest() // new 命令总是返回一个对象,要么是 this 对象,要么是 return 前面跟的对象 // new 调用的是构造函数,阐明 XMLHttpRequest 是一个构造函数
- 初始化 HTTP 申请参数,比方 url,http 申请办法等,但并 (不发送申请)
-
xhr.open() 办法次要供 xhr.send() 办法应用
xhr.open(method, url, async, username, password)
-
参数
- method:http 申请的办法,包含 GET POST HEAD
- url:申请的地址
-
async:是否异步
- true,默认值,异步申请,通常须要调用 onreadystatechange() 办法
- false,对 send() 办法的调用将阻塞,直到响应齐全接管
(2) xhr.send()
-
发送一个 http 申请
xhr.send(body)
- get 申请:get 申请的参数能够间接写在 open() 办法中
-
post 申请:post 申请的参数写在 send() 办法中
-
留神:
- body 参数的数据类型会影响 requestHeader 中的 Content-Type 的默认值,如何手动指定则会笼罩默认值
- 如果 data 是 Document 类型,同时也是 HTML Document 类型,则 content-type 默认值为 text/html;charset=UTF-8; 否则为 application/xml;charset=UTF-8;
- 如果 data 是 DOMString 类型,content-type 默认值为 text/plain;charset=UTF-8;
- 如果 data 是 FormData 类型,content-type 默认值为 multipart/form-data; boundary=[xxx]
- 如果 data 是其余类型,则不会设置 content-type 的默认值
(3) xhr.setRequestHeader()
-
- 指定一个 http 申请的头部,只有在 readState = 1 时能力调用
-
setRequestHeader 能够调用的机会
-
- 在 readyStaet = 1 时
-
- 在 open() 办法之后,send() 办法之前
-
- 其实 1 2 是一个意思
xhr.setRequestHeader(‘name’, ‘value’)
-
-
参数
- name:头部的名称
- value:头部的值
-
留神
- setRequestHeader() 办法能够 ( 屡次调用),值不是 (笼罩 override) 而是 (追加 append)
- setRequestHeader() 只有在 readyState = 1 时能力调用,即 open() 办法之后,send() 办法之前
(4) xhr.getResponseHeader()
-
指定 http 响应头部的值
(5) xhr.abort()
- 勾销以后响应,敞开连贯并且完结任何未决的网络流动
- xhr.abort()会将 readyState 重置为 0
- 利用:勾销申请,在申请耗时太长,响应不再有必要时,调用该办法
-
abort:是终止的意思
(6) xhr.onreadystatecange()
- 在 readyState 状态扭转时触发
- xhr.onreadystatechange() 在 readyState = 3 时,可能屡次调用
- onreadystatechange 都是小写
-
readyState 驼峰
readyState 状态
- UNSENT ————- xhr 对象胜利结构,open() 办法未被调用
- OPEND ————- open() 办法被调用,send() 办法未被调用,setRequestHeader() 能够被调用
- HEADERS_RECEIVED — send() 办法曾经被调用,响应头和响应状态曾经返回
- LOADING ———— 响应体 (response entity body) 正在下载中,此状态下通过 xhr.response 可能曾经有了响应数据
-
NODE ————- 整个数据传输过程完结,不论本次申请是胜利还是失败
(7) xhr.onload
- 申请胜利时触发,此时 readyState = 4
-
留神:重点!!!
- 1. 除了在 xhr.onreadystatechange 指定的回调函数的 readyState = 4 时取值
-
2. 还能够在 xhr.onload 事件中取值
xhr.onload = function() {// 申请胜利 if (xhr.status === 200) {// do successCallback}
}
-
3. 判断 xhr.status === 200 是有坑的,因为胜利时返回的状态码不只有 200,上面的写法更靠谱
xhr.onload = function () {// 如果申请胜利 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { // 304 not modified 资源未被批改 协商缓存 // 2 结尾的状态码,示意申请胜利 //do successCallback }
}
(8) xhr.timeout
- 设置过期工夫
- 问题 1:申请的开始工夫怎么确定?是 (xhr.onloadstart) 事件触发的时候,也就是 xhr.send()调用的时候
- 解析:因为 xhr.open()只是创立了链接,当并没有真正传输数据,只有调用 xhr.send()时才真正开始传输
- 问题 2:什么时候是申请完结?
-
解析:(xhr.loadend) 事件触发时完结
(9) xhr.onprogress 下载进度信息
(10) xhr.upload.onprogress 上传进度信息
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {const present = e.loaded / e.total * 100;
}
} -
XMLHttpRequest 申请案例
XMLHttpRequest 申请案例 ---- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <button id="buttonId"> 点击,申请数据 </button> <script> const button = document.getElementById('buttonId') button.addEventListener('click', handleClick, false) function handleClick() {const xhr = new XMLHttpRequest() xhr.open('GET', 'http://image.baidu.com/channel/listjson?pn=0&rn=30&tag1= 明星 &tag2= 全副 &ie=utf8', true) // open()办法 xhr.setRequestHeader('Content-Type', 'application/json') // setRequestHeader 必须在 open()办法后,send()办法前调用,即 readyState === 1 时 xhr.responseType = 'text' xhr.timeout = 10000 xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) { // 这里通过 this 代替 xhr 其实是一样的 // 因为 this 在运行时确定指向,xhr 实例在调用 onreadystatechange 办法,所以 this 指向 xhr 实例 console.log(JSON.parse(this.responseText)) // 等价于 console.log(JSON.parse(xhr.responseText)) } } xhr.onload = function () {if ((xhr.status >= 200 && xhr.status < 300) || (xhr.status === 304)) {console.log(JSON.parse(xhr.responseText), 'xhr.onload 是在申请实现时触发的回调') } } xhr.send() // 发送申请} </script> </body> </html>
axios
- package.json =>
main: index.js
=> 阐明入口文件是 index.js - index.js =>
module.exports = require('./lib/axios');
(1) axios 申请流程梳理
- createInstance 函数签名:
(config) => axios instance
- var axios = createInstance(defaults),进入 createInstance 函数外部
- <font color=red size=”5″>var context = new Axios(defaultConfig)</font>
-
<font color=red>Axios</font>
function Axios(instanceConfig){...}
是一个构造函数-
实例
-
defaults
- this.defaults = instanceConfig
-
interceptors 对象
-
request 对象
new InterceptorManager() 生成的实例对象
-
response 对象
new InterceptorManager() 生成的实例对象
-
-
-
Axios.prototype
- request
- getUri
- ‘delete’, ‘get’, ‘head’, ‘options’, ‘post’, ‘put’, ‘patch’ 等申请的办法
-
<font color=red>InterceptorManager</font>
-
InterceptorManager
是一个构造函数-
InterceptorManager 构造函数 生成的实例上有 (一个属性)
-
handlers
- 是一个数组
- 数组的成员是这样的对象
{fulfilled: fulfilled, rejected: rejected}
或者null
-
-
InterceptorManager.prototype 上有 (三个办法)
- use
- eject
- forEach
-
-
-
<font color=red>InterceptorManager.prototype.use – 增加拦截器 </font>
- 函数签名:
(fulfilled, rejected) => (this.handlers.length - 1)
-
参数
- fulfilled
- rejected
-
返回值
- (this.handlers.length – 1)
- 示意该对象在 this.handers 数组中的下标,次要用于 InterceptorManager.prototype.eject 办法
-
次要作用:
-
- 向 handlers 中 push
{fulfilled: fulfilled, rejected: rejected}
对象
- 向 handlers 中 push
-
- 返回一个
(this.handlers.length - 1)
作为删除该对象的 ID,其实就是该成员在数组中的下标
- 返回一个
-
- 函数签名:
-
<font color=red>InterceptorManager.prototype.eject – 删除拦截器 </font>
-
参数
- id
- InterceptorManager.prototype.eject(id)
-
次要作用:
- 删除这个 id 对应的在 handlers 数组中的该地位上的对象
- 留神:这里不是真正的删除,而是将这个 id 对应的属性设置成 null,在 forEach 中遍历时会判断筛选过滤掉 null 项
-
-
<font color=red>InterceptorManager.prototype.forEach – 过滤掉 null 成员,遍历执行 fn(h) </font>
-
参数
- fn
- InterceptorManager.prototype.forEach(fn)
-
次要作用:
- 遍历 this.handlers 数组,将数组中值不等于 null 的每个对象作为 fn(h) 的参数并执行
-
实现:
- 次要通过 utils.forEach 来实现
-
-
<font color=red>forEach – utils/forEach</font>
- utils.js 中的 forEach 办法
- forEach(obj, fn)
-
次要作用
- 如果参数 obj 是 null 和 undefined 间接返回,不在往下执行
- 如果参数 obj 是 number,string,boolean,function 则转成数组再进行前面的判断
-
如果参数 obj 是数组(即 number,string,boolean,function.array 都会进行数组的遍历),就循环遍历数组
fn.call(null, obj[i], i, obj)
i 是数组下标
-
如果参数 obj 是对象
fn.call(null, obj[key], key, obj);
key 是对象的 key
-
<font color=blue>mergeConfig</font>
- 函数签名:
(config1, config2) => config
-
参数:
- config1
- config2
-
返回
- config
- 即合并之后的总的 config 对象
- 函数签名:
-
<font color=blue>isObject – utils/isObject</font>
val !== null && typeof val === 'object'
- 如果不是 null 并且 typeof 是一个对象就返回 true
-
<font color=blue>isArray – utils/isArray</font>
Object.prototype.toString.call(val) === '[object Array]'
-
<font color=blue>merge – utils/merge</font>
- 函数签名:
(obj1, obj2, ...) => obj
- 参数:对象或者数组,最好是纯对象
- 返回值:一个合并过后的对象
-
原理
-
- 循环遍历每个参数对象的每一个属性
-
- 如果 result 后果对象这个 key 对应的 value 是对象或者数组,并且参数对象这个 key 对应的 value 也是对象或者数组,就递归执行 merge, 从新合并这两个对象
-
-
如果 result 后果对象这个 key 对应的 value 不是对象或者数组,就间接以参数对象的 key 为 key,value 为 value 赋值给 result 对象
- 这样下一个对象也存在雷同属性,则 result 对象的该属性从新被赋值为前面参数独享的值,即会被从新赋值相当于笼罩
-
-
- 函数签名:
-
<font color=blue>deepMerge – utils/deepMerge</font>
-
和 merge 的区别
- 当参数对象 2 中的一个属性是对象,而参数对象 1 中没有该属性时候,依然递归调用 deepMerge,而不是间接赋值
-
后果就是:
- deepMerge 返回的对象,当参数对象这个属性被批改时,不影响 deepMerge 返回的对象
- merge 返回的对象,当参数对象这个属性被批改时,merge 返回的对象的该属性也会跟着扭转
- 即 浅拷贝和深拷贝的区别
-
-
<font color=blue>transformData</font>
- 函数签名:
(data, headers, fns) =>
- 函数签名:
-
<font color=blue>getDefaultAdapter</font>
- 返回值:adapter
-
次要作用
- 依据不同的环境做不同的申请
- 浏览器:XMLHttpRequest 申请
- node:http https 模块
-
辨别浏览器还是 node 环境
-
浏览器环境
- XMLHttpRequest 存在
- 存在,加载
adapter = require('./adapters/xhr')
-
node 环境
- process 存在
- 存在,加载
adapter = require('./adapters/http')
-
-
<font color=blue size=5>Axios.prototype.get – 以 get 办法举例 </font>
- 函数签名:
(url, data) => this.request(utils.merge(config||{}, {method: method, url: url}))
- this.request 就是调用时所在的对象上的 request => Axios 的实例上的 request => this.request === Axios.prototype.request
- 函数签名:
-
<font color=blue size=5>Axios.prototype.request</font>
- Axios.prototype.request(config)
-
(1) 如果 config 是一个字符串,那么 config = arguments[1] || {};config.url = arguments[0];否则 config = config || {};
-
字符串
- config = arguments[1] || {};
- config.url = arguments[0]
axios('example/url'[, config]) 这样满足 config 是一个 string,则 config 就是第二个参数,第一个参数就是 url
-
对象
- config = config || {}
-
-
(2) 合并 config
- config = mergeConfig(this.defaults, config);
-
(3) 设置 config.method
- 如果 config.method 存在,就转成小写
- 如果 config.method 不存在,然而 his.defaults.method 存在,config.method = this.defaults.method.toLowerCase()
- 以上两个都不存在,默认 config.method = get
-
(4) var chain = [dispatchRequest, undefined];
- 留神这里没有执行 dispatchRequest 函数
-
(5) <font color=blue>dispatchRequest</font>
- 函数签名:
(config) => adapter(config).then(value => value, reason => Promise.reject(reason))
- 返回值:申请实现后的,一个新的 promise
- 函数签名:
- (6) 依据 45:
var chain = [(config) => adapter(config).then(value => value, reason => Promise.reject(reason)), undefined]
- (7) 拦截器先略过
-
(8) <font color=blue>var promise = Promise.resolve(config)</font>
- 将 config 对象转成 promise 对象,该对象是 resolve 状态,其值会作为 then(value => {})办法的第一个胜利回调的参数即 value 的值
-
(9) this.interceptors.request.forEach(interceptor => { chain.unshift(interceptor.fulfilled, interceptor.rejected) })
- 循环遍历 request 中的 this.handlers 数组,将每一个项作为 interceptor 参数,unshift 到 chain 中
- <font color=blue>chain = [申请拦截器的胜利办法,申请拦截器的失败办法,dispatchRequest,undefined]</font>
-
这里要特地留神:
- request: new InterceptorManager()
- response: new InterceptorManager()
-
<font color=blue>this.handlers 在构造函数中,而不是在构造函数的 prototype 上,即 handles 数组在每个实例上,互相独立 </font>
- <font color=blue>axiosInstance.interceptors.request.use 和 axiosInstance.interceptors.response.use</font>
- <font color=blue> 两个 use 是向不同的独立的 handlers 中 push 和 unshift 对象 </font>
- <font color=blue>request 是 unsfift 在后面 </font>
- <font color=blue>response 是 push 在前面 </font>
-
(10) this.interceptors.response.forEach(interceptor => {chain.push(interceptor.fulfilled, interceptor.rejected)})
- 和 9 同理
- 循环遍历 response 中的 handlers 数组,将每一项作为入这里的 forEach 回调函数的参数
- <font color=blue>chain = [申请拦截器的胜利办法,申请拦截器的失败办法,dispatchRequest,undefined, 响应拦截器的胜利办法,响应拦截器的失败办法]</font>
-
(11) while 循环 chain 数组,执行 <font color=blue>promise = promise.then(chain.shift(), chain.shift())</font>
- promise 是传入申请的合并后的最终的配置对象
- 每次从 chain 数组中从前往后取出两个元素
- <font color=blue>var chain = [申请拦截器的胜利办法,申请拦截器的失败办法, (config) => adapter(config).then(value => value, reason => Promise.reject(reason)), undefined, 响应拦截器的胜利办法,响应拦截器的失败办法]</font>
- 留神:这里的 config 就是 (8) 步骤中的 config 被转换成的 promise 对象,该对象就会作为 .then 办法胜利回调的参数即 config
- 失去:
var chain = [申请拦截器的胜利办法,申请拦截器的失败办法, (config) => adapter(config).then(value => value, reason => Promise.reject(reason)), undefined, 响应拦截器的胜利办法,响应拦截器的失败办法] promise = Promise.resolve(config); .then('申请胜利拦挡 2', '申请失败拦挡 2') // 依此向下传递 config , 留神 2 在 1 后面,unshift .then('申请胜利拦挡 1', '申请失败拦挡 1') .then(dispatchRequest, undefined) // 真正发送申请,(config) => adapter(config).then(value => value, reason => Promise.reject(reason)) .then('响应胜利拦挡 1', '响应失败拦挡 1') .then('响应胜利拦挡 2', '响应失败拦挡 2') // 留神 2 在 1 前面,push - (12) 最初返回 promise - then()返回一个新的 promise 实例 - 就又能够通过 .then() 办法获取申请失去的后果值
(2) axios 申请流程,拦挡原理 – 源码
1. package.json => main => index.js => equire(‘./lib/axios’)
1. package.json => main => index.js => equire('./lib/axios')
'use strict';
var utils = require('./utils');·
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');
function createInstance(defaultConfig) {// createInstance(defaultConfig)
// 参数:配置对象
// 作用:创立一个 axios 实例,并返回
var context = new Axios(defaultConfig);
// 生成 axios 实例 context
// 实例属性
// 实例属性:defaults = instanceConfig = 传入的参数对象 defaultConfig
// 实例属性: interceptors 对象
// interceptors.request = new InterceptorManager()
// interceptors.response = new InterceptorManager()
// 实例属性:handlers 一个数组
// 原型对象上的属性:use eject forEach
// use => 向 handlers 增加 {fulfilled: fulfilled, rejected: rejected}对象,返回数组下标,下标用于 eject
// eject => 向 handlers 设置该下标成员为 null,相当于删除,因为在 forEach 中会判断是否为 null
// forEach => 循环 handlers 数组,依此执行 fn(h);
// fn => 是 forEach 传入的参数函数
// h => 是 handlers 中的每个成员,对象 4
// 原型对象上的属性
// request
// getUri
// 'delete', 'get', 'head', 'options', 'post', 'put', 'patch' 等申请的办法
// 总结:// (1) context 对象上有 defaults interceptors request getUri 'delete', 'get', 'head', 'options', 'post', 'put', 'patch' 等申请的办法
// (2) interceptors.request 和 interceptors.response 上有 handlers use eject forEach
var instance = bind(Axios.prototype.request, context);
// bind 办法相似于原声的 bind
// 这里 bind 就是返回一个函数 warp() => Axios.prototype.request.apply(context, [...arguments])
// 所以 instance 是一个函数,这个函数将 request 中的 this 绑定在 context 上并返回执行后果
// var instance = warp() => Axios.prototype.request.apply(context, [...arguments])
utils.extend(instance, Axios.prototype, context);
// 继承原型
// utils.extend 作用:// 将 Axios.prototype 所有属性和办法都赋值给 instance
// 如果是 Axios.prototype 上的办法,还要将 this 绑定到 context
// 最终:instance 办法上具备 Axios.prototype 上的所有属性和办法
utils.extend(instance, context);
// 继承实例
// 将 context 上的所有属性和办法赋值给 instance
return instance;
}
var axios = createInstance(defaults);
// 创立 axios 实例
axios.Axios = Axios;
// Expose Axios class to allow class inheritance
// 能够通过 new axios.Axios()
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Factory for creating new instances
// 总结:创立 axios 实例的三种办法
// 1. axios(configObject)
// axios 自身就是这样一个函数 warp() => Axios.prototype.request.apply(context, [...arguments])
// 2. axios.create(configObject)
// 调用 createInstance(mergeConfig(axios.defaults, instanceConfig))
// 3. new axios.Axios().request(configObject)
axios.Cancel = require('./cancel/Cancel');
// Cancel(message) 是一个构造函数
// 实例
// message
// 原型
// toString
// _CANCEL = true
axios.CancelToken = require('./cancel/CancelToken');
// CancelToken(executor) 是一个构造函数
// 实例
// promise
// reason
// 原型
// throwIfRequested
// 静态方法
// source
// 返回 {token: token, cancel: cancel} 这样一个对象
// new CancelToken(executor)
// 开发中这样调用:cancelToken = new axios.CancelToken(function executor(c) {cancel = c}) !!!!!!!!!!!!!!!!!!!!!!!!!!
// 1. 如果 executor 不是函数,抛错
// 2. this.promise = 一个 promise 对象,始终出于 pending 状态,在调用 resolve(message)时扭转状态
// 3. executor 函数的参数也是一个函数,即 c 函数
// 4. c 函数次要作用就是 resolve(new Cancel(message)) 当执行后,// 5. 在 Axios.prototype.request => dispatchRequest => defaults => getDefaultAdapter
// throwIfCancellationRequested(config)
// 如果 config.cancelToken 存在,config.cancelToken.throwIfRequested() 即 throw this.reason 即 throw new Cancel(message) 即一个 {message: 'xxxx'}
// 5. 在 Axios.prototype.request => dispatchRequest => defaults => getDefaultAdapter => xhr.js 中就行 XMLHttpRequest 申请
// 会判断 config.cancelToken 是否存在
// 存在,用 abort() 中断执行,将 resolve(message)的 message 作为 then()回调的参数 reject(message)
// 6. 总结:// config.cancelToken = {// promise: new Promise(function(resolve){// resolve({ message: '...'})
// }),
// reason: {message: '....'}
// }
axios.isCancel = require('./cancel/isCancel');
// 标记位,用来标记是否勾销
// 以上三个属性次要用来勾销申请
// Expose all/spread
axios.all = function all(promises) {return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
module.exports.default = axios;
// Allow use of default import syntax in TypeScript
// 即能够通过 import axios from 'axios' 形式引入
2. lib/core/Axios.js ———- Axios 中有各种申请办法 和 Axios.prototype.request 办法
3. 以 axios.get(‘www.baidu.com’, {….}) get 申请为例
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
// 下面的代码示意:遍历数组,增加各种办法
// 比方:Axios.prototype.get = function(url, config) {return request(....) }
// 留神:this.request()是在调用时能力确定 this 的指向,不如通过 axios.get()这样的话 this 就是 axios 实例
-----
解析:先看 utils.forEach 这个函数定义
1. 参数:obj 和 fn
2. 如果 obj 是 null 或者 undefined 间接返回中断执行
3. 如果是 number,string,boolean,function,symbol 就把 obj 结构成数组
4. 如果 obj 是数组,即通过 3 解决过的数据类型都是数组 =================> fn.call(null, obj[i], i, obj)
// obj[i]是数组的值
// i 是下标
// obj 是原数组
5. 如果 obj 是对象,=================================================> fn.call(null, obj[key], key, obj)
// obj[key]是对象的属性对应的值
// key 是对象的属性
// obj 是原对象
function forEach(obj, fn) {if (obj === null || typeof obj === 'undefined') {return;}
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj];
}
if (isArray(obj)) {for (var i = 0, l = obj.length; i < l; i++) {fn.call(null, obj[i], i, obj);
}
} else {for (var key in obj) {if (Object.prototype.hasOwnProperty.call(obj, key)) {fn.call(null, obj[key], key, obj);
}
}
}
}
4. Axios.prototype.request – 申请的外围办法,每个办法都会调用该办法
Axios.prototype.request = function request(config) {// axios('example/url'[, config]) 这种状况 config 就是一个 string
// axios 就是 warp() => Axios.prototype.request.apply(context, [...arguments]) 这样一个函数
if (typeof config === 'string') {config = arguments[1] || {}; // 第二个参数是 config 对象
config.url = arguments[0]; // 第一个参数是 url
} else {config = config || {};
}
config = mergeConfig(this.defaults, config);
// 依据不同条件合并传入的参数对象和默认的参数对象,返回合并后的 config
// 暂且不看 mergeConfig 具体的合并规定
if (config.method) {config.method = config.method.toLowerCase();
} else if (this.defaults.method) {config.method = this.defaults.method.toLowerCase();
} else {config.method = 'get';}
// 如果传入的 config 中有 method,应用传入的 method 并转为小写
// 如果不存在,但默认 cofig 中 method 存在,应用默认
// 都不存在,get 办法
var chain = [dispatchRequest, undefined];
// dispatchRequest --------------------------------------------------------------------------------------- 剖析 1
// var chain = [(config) => adapter(config).then(), null]
var promise = Promise.resolve(config);
// 将传入的参数对象 config 转成 promise 对象
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {chain.unshift(interceptor.fulfilled, interceptor.rejected);
// 向 chain 数组循环 unshift
// chain = [申请拦截器的胜利办法 2,申请拦截器的失败办法 2, 申请拦截器的胜利办法 1,申请拦截器的失败办法 1, (config) => adapter(config).then(), null]
});
// this.interceptors.request.forEach --------------------------------------------------------------------- 剖析 2
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {chain.push(interceptor.fulfilled, interceptor.rejected);
// 向 chain 数组循环 push
// chain = [申请拦截器的胜利办法 2,申请拦截器的失败办法 2, 申请拦截器的胜利办法 1,申请拦截器的失败办法 1, (config) => adapter(config).then(), null, 响应拦截器的胜利办法 1,响应拦截器的失败办法 1, 响应拦截器的胜利办法 2,响应拦截器的失败办法 2]
});
while (chain.length) {promise = promise.then(chain.shift(), chain.shift());
// 从左往右一次一次取出两个办法
// promise = Promise.resolve(config);
// .then('申请胜利拦挡 2', '申请失败拦挡 2') // 依此向下传递 config , 留神 2 在 1 后面,unshift
// .then('申请胜利拦挡 1', '申请失败拦挡 1')
// .then(dispatchRequest, undefined) // 真正发送申请,(config) => adapter(config).then(value => value, reason => Promise.reject(reason))
// .then('响应胜利拦挡 1', '响应失败拦挡 1')
// .then('响应胜利拦挡 2', '响应失败拦挡 2') // 留神 2 在 1 前面,push
}
return promise;
// 最初返回 promise
// then()返回一个新的 promise 实例
// 就又能够通过 .then() 办法获取申请失去的后果值};
剖析 1
dispatchRequest
函数签名:(config) => adapter(config).then()
function throwIfCancellationRequested(config) {if (config.cancelToken) {config.cancelToken.throwIfRequested();
}
}
module.exports = function dispatchRequest(config) {throwIfCancellationRequested(config);
// 如果 config 中 cancelToken 存在,就示意勾销申请,throw this.reason = throw new Cancel(message) = throw {messsage: '...'}
config.headers = config.headers || {};
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
config.headers = utils.merge(config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
// tuils.merge(obj1, obj2)
// 循环遍历所有参数对象,并遍历每个参数对象的每个属性
// 把每个属性和值赋值给新的对象
// 如果新对象该属性和参数对象的该属性都是一个对象,递归
// 如果新对象该属性不存在或者不是对象,间接赋值
// 留神辨别 utils.deepMerge 和 utils.merge
// deepMerge => 当新对象中属性不存在,参数对象该属性是一个对象时,不间接赋值,而是浅拷贝
utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
// adapter 会依据浏览器环境或 node 环境应用不同的申请
// 浏览器 => XMLHttpRequest 来判断 => XHR
// node => process 来判断 => http https
return adapter(config).then(function onAdapterResolution(response) {
// 返回申请获取的数据,promise 包装
throwIfCancellationRequested(config);
// 如果 config.cancelToken 存在,终止响应,抛出谬误
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response; // 返回申请后果包装解决后的对象
}, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
剖析 2
InterceptorManager.prototype.forEach
参数:fn
作用:循环 handlers 数组,将每个成员作为毁掉函数的参数,即执行 fn(h)
留神:1. this.handlers 是 [{fulfilled: fulfilled,rejected: rejected}] 这样的数组 或者成员是 null
2. axios.interceptors.requet.handlers 和 axios.interceptors.requet.handlers 是不同的数组,因为是两次执行了 new InterceptorManager() 是不同的实例
- request: new InterceptorManager()
- response: new InterceptorManager
InterceptorManager.prototype.forEach = function forEach(fn) {utils.forEach(this.handlers, function forEachHandler(h) {if (h !== null) {fn(h);
}
});
};
(3) axios 勾销申请 – 源码
-
以这种勾销申请的应用办法为例
const CancelToken = axios.CancelToken; const source = CancelToken.source(); // ----------------------------- source 工厂函数 axios.get('/user/12345', {cancelToken: source.token // --------------------------------------- token}).catch(function (thrown) {if (axios.isCancel(thrown)) {console.log('Request canceled', thrown.message); } else {// handle error} }); // 勾销申请 source.cancel('Operation canceled by the user.'); // ----------------- cancel 函数
1. lib/axios.js
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
2. axios.CancelToken = require(‘./cancel/CancelToken’)
- CancelToken(executor)
-
CancelToken 是一个构造函数
-
实例
- promise
- reason
-
-
留神:<font color=blue> 生成的 cancelToken 实例是要挂载到申请办法的 config 对象中的 </font>
- 像这样
axios.get('/user/12345', { cancelToken: source.token}).catch()
'use strict'; var Cancel = require('./Cancel'); function CancelToken(executor) {if (typeof executor !== 'function') { // executor 必须是一个函数 throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; // this.promise 是一个处于 pending 状态的 promise 对象 }); var token = this; // 固定 this executor(function cancel(message) { // 这里是调用 executor,调用时又传入了一个 cancel 函数作为 executor 的参数 // executor 是传入 CancelToken 的参数函数 // executor 的 (参数) 也是一个 (函数),即 cancel 函数 // 这里是 cancel 函数定义 // cancel 函数的参数是 message if (token.reason) {return;} token.reason = new Cancel(message); // this.resaon 不存在,就 new Cancel(message) // new Cancel(message) 是一个构造函数 // 实例 // messge // 原型 // toString // __CANCEL__ = true // 所以 // reason 就是一个 {message: 'xxxx'} 的对象 resolvePromise(token.reason); // 扭转 this.promise 的状态,由 pending 变为 resolve }); } // CancelToken 总结 // CancelToken 构造函数生成的实例属性有两个 promise 和 reason // token = {// promise: new Promise(resolve => resolve({message: 'xxxxxxx'})), // reason: {messge: 'xxxxxx'} // } // 留神:生成的 cancelToken 实例是要挂载到申请的 config 对象中的 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // `axios.get('/user/12345', { cancelToken: source.token}).catch()` 像这样 !!!!!!!!!!!!!!!!!!!!!!!!!!!! CancelToken.prototype.throwIfRequested = function throwIfRequested() { // reason 存在,就抛错 if (this.reason) {throw this.reason;} }; CancelToken.source = function source() { // 静态方法 source var cancel; var token = new CancelToken(function executor(c) { // 这里的 c 就是下面 executor 函数的参数 cancel 函数 // c 函数次要是:把 this.promise 的状态由 pending 变为 resolve cancel = c; }); return {token: token, // token 是 {promise: new Promise(resolve => resolve({messge: ''})), reason: {message:''}} 这样的对象 cancel: cancel // 勾销函数 }; }; module.exports = CancelToken;
- 像这样
(3) 当 cancel()函数执行时,申请传入的 config 对象中的 cancelToken 属性对象中的 promise 状态变为 resolve 之后,就进入到 dispatchRequest
- <font color=blue> 在各个阶段都须要判断 config 中 cancelToken 是否存在,存在抛错,中断执行 </font>
- 申请前
- 申请时
- 响应后等
module.exports = function dispatchRequest(config) {throwIfCancellationRequested(config);
// 在发送之前,就判断 config 中是否存在 cancelToken
// 如果存在就抛错,中断执行
config.headers = config.headers || {};
// Transform request data
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// Flatten headers
config.headers = utils.merge(config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {// 在 adapter(config) 中 => default.js 中 => adapter 文件夹中的 => 浏览器环境 xhr.js 中
// 做了如下判断
// if (config.cancelToken) {// config.cancelToken.promise.then(function onCanceled(cancel) {// if (!request) {
// return;
// }
// request.abort();
// reject(cancel);
// request = null;
// });
// }
// 留神:下面的 config.cancelToken.promise.then()的 promise 状态的扭转是在调用了 cancel 函数时,变成 fulfilled 状态
// 也就是说:当 cancel 函数执行时,申请才会去 abort 中断请求,并抛错
throwIfCancellationRequested(config);
// 求请实现,失去相应数据时,也判断 config 中是否存在 cancelToken
// 如果存在就抛错,中断执行
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);
// 出错时也判断 config 中是否存在 cancelToken
// 如果存在就抛错,中断执行
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
(4) 在 dispatchRequest => adapter => xhr 中
if (config.cancelToken) {
// 如果 cancelToken 存在
config.cancelToken.promise.then(function onCanceled(cancel) {// 当 cancel()函数执行时 => config.cancelToken.promise 状态曾经是 resolve({message: message})
// 即执行 cancel 函数时,进行了 resolve 操作
if (!request) {return;}
request.abort(); // 中断请求
reject(cancel); // 抛出谬误,message
// Clean up request
request = null;
});
}
实例使用:axios 中的勾销申请 CancelToken 在 vue 我的项目中的使用
-
需要 1
- 如果每个页面只有一个申请,那么能够通过 mixin 把 token 和 cancel 注入到每个组件的 data 中
-
需要 2
- 每次路由跳转,都勾销掉上个路由的所有申请
-
- 路由跳转时,利用路由守卫 beforeEach,执行 store 中的所有 cancel 勾销函数
-
-
在 axios 的申请拦挡中
- new axios.CancelToken(c => cancel = c)把实例赋值给 config.cancelToken 属性;
- 将 cancel 函数 push 到 store 中存储
-
我的项目源码 – 源码剖析地址
- axios 源码剖析 – 我的项目源码地址
- 具体文件夹:
src => SOURCE-CODE-ANALYSIS => AXIOS 文件夹中
材料
川神 https://juejin.im/post/684490…
重点 cancelToken https://juejin.im/post/684490…
https://juejin.im/post/684490…
axios 官网 https://github.com/axios/axios
vue 和 react 中如何优雅地应用 axios 勾销申请 https://juejin.im/post/684490…