axios headers deep merge에 대해
FrontEnd | | 14분 읽기

axios 인스턴스를 만드는 부분 → lib/axios.js
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
*
* @returns {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig);
const instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// Copy context to instance
utils.extend(instance, context, null, {allOwnKeys: true});
// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}instance를 반환하고 있는데 이는
const instance = bind(Axios.prototype.request, context); // this 바인딩 해주고 있음즉 instance는 Axios.prototype.request 자체라고 할 수 있음
= new Axios(defaultConfig)를 this로 여기는
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};instance.create 부분에서 instance 객체에 create라는 새로운 메소드를 직접 추가하는 것
우리는 이 instance를 갖고 다음과 같이 사용
const axiosClient = axios.create({
baseURL,
timeout: 5 * 1_000,
headers: {
'tenant-id': 1, // 커머스모듈에서 테넌트를 구분하기 위해 전달 ( cashdeal = 1 )
},
});따라서 초기에 create를 하여 Axios 인스턴스를 생성할 때 create에 전달한 instanceConfig와 axios 초기 config인 defaultConfig가 병합됨
// lib/core/Axios.js
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
/**
* Dispatch a request
*
* @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
* @param {?Object} config
*
* @returns {Promise} The Promise to be fulfilled
*/
async request(configOrUrl, config) {
try {
return await this._request(configOrUrl, config);
} catch (err) {
if (err instanceof Error) {
let dummy;
Error.captureStackTrace ? Error.captureStackTrace(dummy = {}) : (dummy = new Error());
// slice off the Error: ... line
const stack = dummy.stack ? dummy.stack.replace(/^.+\n/, '') : '';
if (!err.stack) {
err.stack = stack;
// match without the 2 top stack lines
} else if (stack && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) {
err.stack += '\n' + stack
}
}
throw err;
}
}
_request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
config = mergeConfig(this.defaults, config);
const {transitional, paramsSerializer, headers} = config;
...
}
}this.defaults는 instanceConfig와 axios 초기 config인 defaultConfig가 병합된 config
request에서는 객체 혹은 문자열을 받고 있음
_request를 봤을 때 문자열을 받으면 config url에 병함
객체라면 config에 객체를 덮어씌움
config = mergeConfig(this.defaults, config); 이렇게 axios의 config를 합치고 있는데
//lib/core/mergeConfig.js
/**
* Config-specific merge-function which creates a new config-object
* by merging two configuration objects together.
*
* @param {Object} config1
* @param {Object} config2
*
* @returns {Object} New object resulting from merging config2 to config1
*/
export default function mergeConfig(config1, config2) {
// eslint-disable-next-line no-param-reassign
config2 = config2 || {};
const config = {};
utils.forEach(Object.keys(Object.assign({}, config1, config2)), function computeConfigValue(prop) {
const merge = mergeMap[prop] || mergeDeepProperties;
const configValue = merge(config1[prop], config2[prop], prop);
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
});
return config;
}defaultConfig와 instanceConfig를 assign을 통해 합치고 key를 얻어내서 utils를 통해 headers 키를 prop로 받아와 mergeMap[prop](mergeMap[”headers”])를 찾음
forEach는 다음과 같음
/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*
* @param {Boolean} [allOwnKeys = false]
* @returns {any}
*/
function forEach(obj, fn, {allOwnKeys = false} = {}) {
if (isArray(obj)) {
// Iterate over array values
for (i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
}
}call을 통해 받은 fn에 매개변수로 obj[i] 전달
const headersToObject = (thing) => thing instanceof AxiosHeaders ? { ...thing } : thing;
/**
* Config-specific merge-function which creates a new config-object
* by merging two configuration objects together.
*
* @param {Object} config1
* @param {Object} config2
*
* @returns {Object} New object resulting from merging config2 to config1
*/
export default function mergeConfig(config1, config2) {
// eslint-disable-next-line no-param-reassign
config2 = config2 || {};
const config = {};
function getMergedValue(target, source, caseless) {
if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
return utils.merge.call({caseless}, target, source);
} else if (utils.isPlainObject(source)) {
return utils.merge({}, source);
} else if (utils.isArray(source)) {
return source.slice();
}
return source;
}
// eslint-disable-next-line consistent-return
function mergeDeepProperties(a, b, caseless) {
if (!utils.isUndefined(b)) {
return getMergedValue(a, b, caseless);
} else if (!utils.isUndefined(a)) {
return getMergedValue(undefined, a, caseless);
}
}
const mergeMap = {
headers: (a, b) => mergeDeepProperties(headersToObject(a), headersToObject(b), true)
};
utils.forEach(Object.keys(Object.assign({}, config1, config2)), function computeConfigValue(prop) {
const merge = mergeMap[prop] || mergeDeepProperties;
const configValue = merge(config1[prop], config2[prop], prop);
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
});
return config;
}이를 통해서 create할 때 생성한 config와 axiosClient를 호출할 때 매개변수로 전달해준 config가 병합
→ headers, params, timeout 등 설정은 key 기준으로 병합됨
→ 키가 겹치지 않으면 누적되고, 겹치면 우선순위는 config2 (즉, 요청 시 전달한 설정)
→ 두 config의 모든 키를 순회하면서 각 속성별로 적절한 병합 로직을 적용
참고자료
https://github.com/axios/axios/issues/2204