Implementation Axios
作者:互联网
曾经想过实现一个 mini 版的 axios,终于达成目标了!
mini axios
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
} else {
}
};
xhr.open(method, url, true);
xhr.send(body);
也许你会捂不住嘴直呼这 tm 不是 Ajax(Async JavaScript And XML)吗~,跟 axios 有毛关系。
当然,如果没看过 axios 源码,确实很难让 axios 的浏览器实现和 Ajax 扯上联系,axios 不仅包装了 XMLHttpRequest,而且还很彻底,多彻底呢?
XMLHttpRequest 的属性:
- onreadystatechange
- readyState
- response
- responseText
- responseType
- responseURL
- responseXML
- status
- statusText
- timeout
- upload
- withCredentials
XMLHttpRequest 的方法:
- abort()
- getAllResponseHeaders()
- getResponseHeader()
- open()
- openRequest()
- overrideMimeType()
- send()
- setRequestHeader()
能用上的几乎都用上了!一览无余,是否有似曾相识的感觉!在没有 axios 的时代,可是手撸 http 请求的呢(得瑟)。怎么,还是还是觉得太空旷难以和 axios 的使用或实现建立联系?别慌!慢慢来,让我们从 axios 的使用和功能慢慢回忆。
Axios
首先需要知道 axios 是一个基于 Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生 XHR 的封装,只不过它是 Promise 的实现版本,有以下特点:
- 在浏览器端使用 XMLHttpRequest 对象通讯
- 从 node.js 创建 http 请求
- 支持 Promise API
- 支持请求和响应的拦截器
- 支持请求数据和响应数据的转换
- 支持请求的取消
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
看了一大堆不如 demo 来得直接:
超级简单的
axios("/user?ID=12345").then((res) => {
console.log(res);
});
几乎涵盖所有所有用法
// Set config defaults when creating the instance
const instance = axios.create(config);
// Append interceptors with instance
const myInterceptor = instance.interceptors.request.use(function () {/*...*/ });
instance.interceptors.request.eject(myInterceptor);
// Alter defaults after instance has been created
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// Alter properties after alert defaults in request
instance.get('/longRequest', {
timeout: 5000,
...
}).then((res: response) => { });
// config
const config = {
url: "/xxx",
method: "get", // default
baseURL: "https://some-domain.com/api/",
transformRequest: [
function (data, headers) {
return data;
},
],
transformResponse: [
function (data) {
return data;
},
],
headers: { "X-Requested-With": "XMLHttpRequest" },
params: {},
paramsSerializer: function (params) {
return Qs.stringify(params, { arrayFormat: "brackets" });
},
data: {},
timeout: 1000,
withCredentials: false, // default
adapter: function (config) {},
auth: {},
responseType: "json", // default
responseEncoding: "utf8", // default
xsrfCookieName: "XSRF-TOKEN", // default
xsrfHeaderName: "X-XSRF-TOKEN", // default
onUploadProgress: function (progressEvent) {},
onDownloadProgress: function (progressEvent) {},
maxContentLength: 2000,
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
maxRedirects: 5, // default
socketPath: null, // default
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
proxy: {},
cancelToken: new CancelToken(function (cancel) {}),
};
// response model
interface response {
data: {};
status: 200;
statusText: "OK";
headers: {};
config: {};
request: {};
}
上述 instance 实例对于 axios 同样适用,有没有发现很多属性和方法 XMLHttpRequest 中有 axios 中同样也有呢?
没错 axios 中大部分的核心功能就是基于此的,下面看看是怎么实现 axios 的核心功能的吧!
核心实现
首先,用 create-react-app 创建了个简单的 demo 用于模拟查看 axios 的具体调用逻辑
useEffect(() => {
debugger;
Axios.get("www.biying.com").then((res) => {
console.log(res);
});
}, []);
demo 中 get 请求的调用栈如下图
大致可以分为三个阶段:
merge config
左边的小红框
transform
转换各种 data
request
依据 adapter 发送真实请求
当然这只是宏观上的认识,具体实现还得从源码入手
Index
https://github.com/axios/axios/blob/master/index.js
入口直接 require 到 lib 目录下
module.exports = require("./lib/axios");
axios
https://github.com/axios/axios/blob/master/lib/axios.js
直接导出 axios
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
并且,导出的 axios 是默认配置 defaults 的实例对象
// Create the default instance to be exported
var axios = createInstance(defaults);
然后对 axios 对象做了扩展,create 方法,Axios 类,CancelToken 等等
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Factory for creating new instances
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Expose Cancel & CancelToken
axios.Cancel = require("./cancel/Cancel");
axios.CancelToken = require("./cancel/CancelToken");
axios.isCancel = require("./cancel/isCancel");
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
createInstance:
创建 axios 实例,并绑定 context 上下文
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
* @return {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
defaults:
https://github.com/axios/axios/blob/master/lib/defaults.js
默认配置中包含适配器(根据环境决定用什么发送请求),发送请求前对 data 和 headers 的转换函数,接受请求后对 data 的转换函数等
var defaults = {
adapter: getDefaultAdapter(),
transformRequest: [function transformRequest(data, headers) {}],
transformResponse: [function transformResponse(data) {}],
timeout: 0,
xsrfCookieName: "XSRF-TOKEN",
xsrfHeaderName: "X-XSRF-TOKEN",
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: function validateStatus(status) {},
};
defaults.headers = {
common: {},
};
Axios
https://github.com/axios/axios/blob/master/lib/core/Axios.js
Axios():
构造函数初始化 defauls 属性和请求响应拦截器
/**
* Create a new instance of Axios
*
* @param {Object} instanceConfig The default config for the instance
*/
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
request(核心):
request() 方法实际执行 dispatchRequest 时会将请求拦截和响应拦截中加入 chain 队列的两端,从而实现一个promise 调用链
/**
* Dispatch a request
*
* @param {Object} config The config specific for this request (merged with this.defaults)
*/
Axios.prototype.request = function request(config) {
// Allow for axios('example/url'[, config]) a la fetch API
...
// Set config.method
...
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
InterceptorManager:
(请求和响应)拦截器其实就是基于队列,实现的一个发布订阅模型
function InterceptorManager() {
this.handlers = [];
}
订阅:入栈的对象的两个 key 所对应的 value 分别对应 promise 中的 resoveleFn 和 rejectedFn 回调
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
});
return this.handlers.length - 1;
};
发布:遍历 handlers 数组中个每个 item,在 request()中会加入 chain 的两端
/**
* Iterate over all the registered interceptors
*
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
*
* @param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
取消:对应位置设置为 null(不是直接删除这个位置的元素),chain 链中不会执行
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
dispatchRequest:
https://github.com/axios/axios/blob/master/lib/core/dispatchRequest.js
核心逻辑就是,调用 adapter 执行正真的请求
/**
* Dispatch a request to the server using the configured adapter.
*
* @param {object} config The config that is to be used for the request
* @returns {Promise} The Promise to be fulfilled
*/
module.exports = function dispatchRequest(config) {
// Ensure headers exist
...
// Transform request data
...
// Flatten headers
...
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
...
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
},
function onAdapterRejection(reason) {
...
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
return Promise.reject(reason);
});
};
getDefaultAdapter()
简单直接,对浏览器端和 node 判断
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== "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");
}
return adapter;
}
xhrAdapter:
https://github.com/axios/axios/blob/master/lib/adapters/xhr.js
返回一个 promise,核心逻辑还是对 XMLHttpRequest 的运用,是不是和开篇殊途同归呢
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
...
var request = new XMLHttpRequest();
// HTTP basic authentication
...
// Set the request timeout in MS
request.timeout = config.timeout;
// Listen for ready state
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by one rror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// Prepare the response
...
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
// Clean up request
request = null;
};
// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
};
// Handle low level network errors
request.onerror = function handleError() {
// Real errors are hidden from us by the browser
// one rror should only fire if it's a network error
reject(createError('Network Error', config, null, request));
// Clean up request
request = null;
};
// Handle timeout
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
request));
// Clean up request
request = null;
};
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
...
// Add headers to the request
if ('setRequestHeader' in request) {
...
}
// Add withCredentials to request if needed
...
// Add responseType to request if needed
...
// Handle progress if needed
...
// Not all browsers support upload events
...
// Send the request
request.send(requestData);
});
};
总结
沿着本文的思路顺便画了张图
references
作者:shanejix
出处:https://www.shanejix.com/posts/Implementation Axios/
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
声明:转载请注明出处!
标签:function,axios,return,Implementation,request,Axios,config,response 来源: https://www.cnblogs.com/shanejix/p/15893681.html