axios做为他们组织工作中的常见的ajax允诺库,做为后端技师的他们总之是想一探到底,axios到底是怎样去构架整座架构,尾端的圣夫龙、转接器、 中止允诺那些都是他们时常采用的。
序言
虽然axios源代码许多并非很关键的方式,所以许多方式为的是考量相容性,并没考量到用es6 的句法去写。第一集主要就是快来剖析axios的主要就业务流程,他用es6改写莫雷县axios
圣夫龙
转接器
中止允诺
圣夫龙
三个axios示例上有三个圣夫龙,三个是允诺圣夫龙, 接着积极响应圣夫龙。他们下看下官方网站的用语:
加进圣夫龙
// 加进允诺圣夫龙axios.interceptors.request.use(function(config) {
// 在推送允诺以后做些甚么 returnconfig;
}, function(error) {
// 对允诺严重错误做些甚么 return Promise.reject(error);
});
去除圣夫龙
const myInterceptor = axios.interceptors.request.use(function () {/*…*/});
axios.interceptors.request.eject(myInterceptor);
其实源代码中就是,所有圣夫龙的执行 所以说肯定有三个forEach方式。
思路理清楚了,现在他们就开始去写吧。代码我就直接发出来,接着我在下面注解。
export class InterceptorManager{
constructor() {
// 存放所有圣夫龙的栈 this.handlers = []
}
use(fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected,
})
//返回id 便于中止 return this.handlers.length – 1}
// 中止三个圣夫龙 eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null}
}
// 执行栈中所有的hanlder forEach(fn) {
this.handlers.forEach((item) => {
// 这里为的是过滤已经被中止的圣夫龙,因为已经中止的圣夫龙被置null if(item) {
fn(item)
}
})
}
}
圣夫龙这个类他们已经初步实现了,现在他们去实现axios 这个类,还是先看下官方文档,先看用语,再去分析。
axios(config)
// 推送 POST 允诺axios({
method: post,
url: /user/12345,
data: {
firstName: Fred,
lastName: Flintstone}
});
axios(url[, config])// 推送 GET 允诺(默认的方式)
axios(/user/12345);
Axios 这个类最核心的方式其实还是 request 这个方式。他们先看下实现吧
class Axios{
constructor(config) {
this.defaults = config
this.interceptors = {
request:newInterceptorManager(),
response: newInterceptorManager(),
}
}
// 推送三个允诺 request(config) {
// 这里呢其实就是去处理了 axios(url[,config]) if (typeof config == string) {
config = arguments[1] || {}
config.url = arguments[0]
} else{
config = config || {}
}
// 默认get允诺,并且都转成小写 if(config.method) {
config.method = config.method.toLowerCase()
} else{
config.method = get}
// dispatchRequest 就是推送ajax允诺 const chain = [dispatchRequest, undefined]
// 发生允诺以后加入拦截的 fulfille 和reject 函数 this.interceptors.request.forEach((item) => {
chain.unshift(item.fulfilled, item.rejected)
})
// 在允诺之后增加 fulfilled 和reject 函数 this.interceptors.response.forEach((item) => {
chain.push(item.fulfilled, item.rejected)
})
// 利用promise的链式调用,将参数一层一层传下去 let promise = Promise.resolve(config)
//接着我去遍历 chain while(chain.length) {
// 这里不断出栈 直到结束为止promise = promise.then(chain.shift(), chain.shift())
}
returnpromise
}
}
这里其实就是体现了axios设计的巧妙, 维护三个栈结构 + promise 的链式调用 实现了 圣夫龙的功能, 可能有的小伙伴到这里还是并非很能理解,我还是给大家画三个草图去模拟下这个过程。
假设我有1个允诺圣夫龙handler和1个积极响应圣夫龙handler
一开始他们栈中的数据就三个
这个没甚么问题,虽然有圣夫龙的存在,如果存在的话,那么他们就要往这个栈中加数据,允诺圣夫龙顾名思义要在允诺以后所以是unshift。加完允诺圣夫龙他们的栈变成了这样
没甚么问题,接着允诺结束后,他们又想对允诺之后的数据做处理,所以积极响应拦截的数据自然是push了。这时候栈结构变成了这样:
接着遍历整座栈结构,每次出栈都是一对出栈, 因为promise 的then 就是 三个成功,三个失败嘛。遍历结束后,返回经过所有处理的promise,接着你就可以拿到最终的值了。
adapter
Adapter: 英文解释是转接器的意思。这里我就不实现了,我带大家看一下源代码。adapter 做了一件事非常简单,就是根据不同的环境 采用不同的允诺。如果用户自定义了adapter,就用config.adapter。否则就是默认是default.adpter.
varadapter = config.adapter || defaults.adapter;
returnadapter(config).then() …
继续往下看deafults.adapter做了甚么事情:
function getDefaultAdapter() {
varadapter;
if (typeofXMLHttpRequest !==undefined) {
// For browsers use XHR adapter adapter = require(./adapters/xhr);
} else if (typeof process !== undefined && Object.prototype.toString.call(process) === [object process]) {
// For node use HTTP adapter adapter = require(./adapters/http);
}
returnadapter;
}
其实就是做个选择:如果是浏览器环境:就是用xhr 否则就是node 环境。判断process是否存在。从写代码的角度来说,axios源代码的这里的设计可扩展性非常好。有点像设计模式中的转接器模式, 因为浏览器端和node 端 推送允诺其实并不一样, 但是他们不关键,他们不去管他的内部实现,用promise包一层做到对外统一。所以 他们用axios 自定义adapter 器的时候, 一定是返回三个promise。ok允诺的方式我在下面模拟写出。
cancleToken
我首先问大家三个问题,中止允诺原生浏览器是怎么做到的?有三个abort 方式。可以中止允诺。那么axios源代码肯定也是运用了这一点去中止允诺。现在浏览器其实也支持fetch允诺, fetch可以取消允诺?许多同学说是不可以的,其实并非?fetch 结合 abortController 可以实现中止fetch允诺。他们看下例子:
const controller = newAbortController();
const{ signal } = controller;
fetch(“http://localhost:8000”, { signal }).then(response => {
console.log(`Request 1 is complete!`);
}).catch(e => {
console.warn(`Fetch 1 error: ${e.message}`);
});
// Wait 2 seconds to abort both requestssetTimeout(() => controller.abort(), 2000);
但是这是个实验性功能,可恶的ie。所以他们这次还是用原生的浏览器xhr基于promise简单的封装一下。代码如下:
export function dispatchRequest(config) {
return new Promise((resolve, reject) => {
const xhr = newXMLHttpRequest()
xhr.open(config.method, config.url)
xhr.onreadystatechange = function() {
if (xhr.status >= 200 && xhr.status <= 300 && xhr.readyState === 4) {
resolve(xhr.responseText)
} else{
reject(失败了)
}
}
if(config.cancelToken) {
// Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) {
if(!xhr) {
return}
xhr.abort()
reject(cancel)
// Clean up request xhr = null})
}
xhr.send()
})
}
Axios 源代码里面做了许多处理, 这里我只做了get处理,我主要就的目的就是为的是axios是怎样中止允诺的。先看下官方用语:
主要就是两种用语:
采用 cancel token 中止允诺
constCancelToken = axios.CancelToken;
constsource = CancelToken.source();
axios.get(/user/12345, {
cancelToken: source.token
}).catch(function(thrown) {
if(axios.isCancel(thrown)) {
console.log(Request canceled, thrown.message);
} else{
// 处理严重错误}
});
axios.post(/user/12345, {
name: new name}, {
cancelToken: source.token
})
// 中止允诺(message 参数是可选的)source.cancel(Operation canceled by the user.);
还可以通过传递三个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
constCancelToken = axios.CancelToken;
letcancel;
axios.get(/user/12345, {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收三个 cancel 函数做为参数cancel = c;
})
});
// cancel the requestcancel();
看了官方用语 和结合axios源代码:我给出以下实现:
export class cancelToken{
constructor(exactor) {
if (typeof executor !== function) {
throw new TypeError(executor must be a function.)
}
// 这里其实将promise的控制权 交给 cancel 函数 // 同时做了防止多次重复cancel 以后 Redux 还有React 源代码中也有类似的案列 constresolvePromise;
this.promise = new Promise(resolve => {
resolvePromise = resolve;
})
this.reason = undefined;
constcancel = (message) => {
if(this.reason) {
return;
}
this.reason = cancel+ message;
resolvePromise(this.reason);
}
exactor(cancel)
}
throwIfRequested() {
if(this.reason) {
throw this.reason
}
}
// source 其实本质上是三个句法糖 里面做了封装 static source() {
constcancel;
const token = new cancelToken(function executor(c) {
cancel = c;
});
return{
token: token,
cancel: cancel
};
}
}
截止到这里大体axios 大体功能已经给出。
接下来我就测试下我的记事本axios 有没甚么问题?
<script type=“module”>
import Axios from ./axios.js;
const config = { url:http://101.132.113.6:3030/api/mock}
const axios = newAxios();
axios.request(config).then(res => {
console.log(res,0000)
}).catch(err => {
console.log(err)
})
打开浏览器看一下结果:
成功了ok, 接着我来测试一下圣夫龙的功能:代码更新成下面这样:
import Axios from ./axios.js;
const config = { url:http://101.132.113.6:3030/api/mock}
const axios = newAxios();
// 在axios 示例上挂载属性consterr = () => {}
axios.interceptors.request.use((config)=> {
console.log(我是允诺圣夫龙1)
config.id = 1;
returnconfig
},err )
axios.interceptors.request.use((config)=> {
config.id =2 console.log(我是允诺圣夫龙2)
returnconfig
},err)
axios.interceptors.response.use((data)=> {
console.log(我是积极响应圣夫龙1,data )
data += 1;
returndata;
},err)
axios.interceptors.response.use((data)=> {
console.log(我是积极响应圣夫龙2,data )
returndata
},err)
axios.request(config).then(res => {
// console.log(res,0000) // return res;}).catch(err => {
console.log(err)
})
ajax 允诺的结果 我是resolve(1) ,所以他们看下输出路径:
没甚么问题, 积极响应后的数据我加了1。
接下来我来是中止允诺的两种方式
// 第一种方式
let cancelFun = undefined;
const cancelInstance = new cancelToken((c)=>{
cancelFun = c;
});
config.cancelToken = cancelInstance;
// 50 ms 就中止允诺
setTimeout(()=>{
cancelFun(中止成功)
},50)
第二种方式:
const { token, cancel } = cancelToken.source();
config.cancelToken = token;
setTimeout(()=>{
cancel()
},50)
结果都是OK的,至此axios简单源代码终于搞定了。
反思
第一集文章只是把axios源代码的大体业务流程走了一遍, axios源代码内部还是做了许多兼容比如:配置优先级:他有三个mergeConfig 方式, 还有数据转换器。不过那些不影响他们对axios源代码的整体剖析, 源代码中其实有三个createInstance,至于为甚么有?我觉得就是为的是可扩展性更好, 将来有啥新功能,直接在原有axios的示例的原型链上去增加,代码可维护性强, axios.all spread 都是示例new出来再去挂的,不过都很简单,没啥的。有兴趣大家自行阅读。
作者:FE_FLY
juejin.cn/post/6973257605367988260