Skip to main content

axios

基础使用

import axios from "axios";

// 创建一个axios实例
const service = axios.create({
baseURL: "/api",
timeout: 10000,
});
// 请求拦截
service.interceptors.request.use(
function (config) {
// 可以在这里自定义 header
// config.headers.token = 'token';
return config;
},
function (error) {
return Promise.reject(error);
}
);
// 响应拦截
service.interceptors.response.use(
function (response) {
// 可以在这里检查返回数据的状态
return response;
},
function (error) {
return Promise.reject(error);
}
);
export default service;

入口

以下源码参考自 axios v1.x。入口文件是 lib/axios.js

var utils = require("./utils");
var bind = require("./helpers/bind");
var Axios = require("./core/Axios");
var mergeConfig = require("./core/mergeConfig");
var defaults = require("./defaults");
var formDataToJSON = require("./helpers/formDataToJSON");

// 创建实例
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);
// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};

return instance;
}

var axios = createInstance(defaults);
axios.Axios = Axios;

// Expose Cancel & CancelToken
axios.CanceledError = require("./cancel/CanceledError");
axios.CancelToken = require("./cancel/CancelToken");
axios.isCancel = require("./cancel/isCancel");
axios.VERSION = require("./env/data").version;
axios.toFormData = require("./helpers/toFormData");

// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError;

// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require("./helpers/spread");

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

从入口其实可以初步看到几个有意思的点:

all

通过 Promise.all 去执行所有请求函数,实现并发请求

spread

// ./helpers/spread
module.exports = function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};

从源码看到这个方法其实就起到一个解构的作用,不过现在函数参数其实已经支持解构,所以其实这个方法作用不大了

utils

这个是 axios 项目中会经常用到的一些工具函数,比如入口代码中的 extend,还有 merge

  • extend 可以当做实例插件,会将一些内置属性和方法追加到实例上
  • merge 就是对象合并,不会他会通过深拷贝生成一个新的对象

createInstance

这个函数的作用是先将传入的配置信息和默认配置合并,然后基于 Axios.prototype.request 生成一个新的 axios 实例并返回。主要看下 Axios.prototype.request 源码:

Axios.prototype.request = function request(configOrUrl, config) {
// ...
config = mergeConfig(this.defaults, config);
var requestInterceptorChain = [];
var synchronousRequestInterceptors = true;
// 收集请求拦截器
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
if (
typeof interceptor.runWhen === "function" &&
interceptor.runWhen(config) === false
) {
return;
}
synchronousRequestInterceptors =
synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(
interceptor.fulfilled,
interceptor.rejected
);
});
var responseInterceptorChain = [];
// 收集响应拦截器
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
// 创建和执行任务链,任务链指 [请求拦截 -> 请求 -> 响应拦截]
var promise;
if (!synchronousRequestInterceptors) {
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
var newConfig = config;
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break;
}
}
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
while (responseInterceptorChain.length) {
promise = promise.then(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
);
}
return promise;
};

拦截器

上述函数中可以看到拦截器相关的代码,axios 拦截器分为请求拦截器和响应拦截器,其实原理很简单,就是一个执行链,在执行请求函数前后增加对应的逻辑,达到拦截效果

[请求拦截1,请求拦截2] --> [请求函数] --> [响应拦截1,响应拦截2]

适配器

根据环境选择 xhr 或 http 处理请求。这部分是在具体的请求处理函数(dispatchRequest)中体现

dispatchRequest 关键代码如下:

function dispatchRequest(config) {
// config 设置,包括 headers 等
// ...
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(
function onAdapterResolution(response) {
// ...
// response.data 转换逻辑
return response;
},
function onAdapterRejection(reason) {
if (!isCancel(reason)) {
if (reason && reason.response) {
// ...
// response.data 转换逻辑
return response;
}
}
return Promise.reject(reason);
}
);
}

适配器源码如下:

// defaults.adapter
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;
}

中断请求

axios 可以通过以下方式来中断请求:

const controller = new AbortController();
axios
.get("/foo", {
signal: controller.signal,
})
.then(function (response) {
//...
});
// 取消请求
controller.abort();

AbortController 接口表示一个控制器对象,允许中止一个或多个 Web 请求,参考 https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController

关键代码如下:

// adapters/xhr.js
// ...
// cancelToken 是 v0.22.0 之前使用的方法,1.x 版本有兼容
if (config.cancelToken || config.signal) {
// 发现 signal 不为空,会定义一个中断请求事件
onCanceled = function (cancel) {
if (!request) {
return;
}
reject(
!cancel || cancel.type ? new CanceledError(null, config, req) : cancel
);
// 调用原生 abort 取消请求
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted
? onCanceled()
: // 监听该事件
config.signal.addEventListener("abort", onCanceled);
}
}

可以看到不论是旧版 cancelToken 还是新版的方式,都是通过发布订阅模式来实现中断请求

TODO

  • 防御 CSRF