- 下图为 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 anyconst [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 requestcancel();
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.jsimport 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 = trueaxios.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/spreadaxios.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 和 fn2. 如果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); });};
剖析2InterceptorManager.prototype.forEach 参数:fn作用:循环handlers数组,将每个成员作为毁掉函数的参数,即执行 fn(h)留神:1. this.handlers是 [{fulfilled: fulfilled,rejected: rejected}] 这样的数组 或者成员是 null2. axios.interceptors.requet.handlers 和 axios.interceptors.requet.handlers 是不同的数组,因为是两次执行了 new InterceptorManager() 是不同的实例 - request: new InterceptorManager() - response: new InterceptorManagerInterceptorManager.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; // 固定thisexecutor(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() {// 静态方法 sourcevar 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...