axios 源码解析
lib/helpers
bind
module.exports = function bind(fn, thisArg) {
// 返回一个function
return function wrap() {
// arguments 为调用wrap的参数数组
var args = new Array(arguments.length);
// 将参数按标准数组输出
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
// 传入fn的上下文切换为thisArg,传入参数args并执行
return fn.apply(thisArg, args);
};
};
buildURL
module.exports = function buildURL(url, params, paramsSerializer) {
/*eslint no-param-reassign:0*/
if (!params) {
return url;
}
var serializedParams;
if (paramsSerializer) {
serializedParams = paramsSerializer(params);
} else if (utils.isURLSearchParams(params)) {
serializedParams = params.toString();
} else {
var parts = [];
utils.forEach(params, function serialize(val, key) {
if (val === null || typeof val === 'undefined') {
return;
}
// 将所有val改成数组的形式,方便下面统一处理
if (utils.isArray(val)) {
key = key + '[]';
} else {
val = [val];
}
utils.forEach(val, function parseValue(v) {
if (utils.isDate(v)) {
v = v.toISOString();
} else if (utils.isObject(v)) {
v = JSON.stringify(v);
}
parts.push(encode(key) + '=' + encode(v));
});
});
serializedParams = parts.join('&');
}
if (serializedParams) {
var hashmarkIndex = url.indexOf('#');
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex);
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
}
return url;
};
- 首先判断有没有传入处理params的函数,有的话,调用此函数处理params
- 判断是否为URLSearchParams的实例,是的话,说明这个是标准的URLSearch对象,此时需要转化为string
- 设置空数组,格式化数据,拼接成key=val字符串,拼接成serializedParams
- 如果存在serializedParams,删除hash后面的内容,并且拼接serializedParams
function encode(val) {
return encodeURIComponent(val).
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, '+').
replace(/%5B/gi, '[').
replace(/%5D/gi, ']');
}
之前对key后面拼接了[]字符,因此把一些不必要的转码单独转回来
combineURLs
module.exports = function combineURLs(baseURL, relativeURL) {
return relativeURL
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
: baseURL;
};
合并url,如果存在相对路径的url,那么拼接baseURL和relativeURL,否则返回baseURL
cookies
module.exports = (
utils.isStandardBrowserEnv() ?
// Standard browser envs support document.cookie
(function standardBrowserEnv() {
return {
write: function write(name, value, expires, path, domain, secure) {
var cookie = [];
cookie.push(name + '=' + encodeURIComponent(value));
if (utils.isNumber(expires)) {
cookie.push('expires=' + new Date(expires).toGMTString());
}
if (utils.isString(path)) {
cookie.push('path=' + path);
}
if (utils.isString(domain)) {
cookie.push('domain=' + domain);
}
if (secure === true) {
cookie.push('secure');
}
document.cookie = cookie.join('; ');
},
read: function read(name) {
var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'));
return (match ? decodeURIComponent(match[3]) : null);
},
remove: function remove(name) {
this.write(name, '', Date.now() - 86400000);
}
};
})() :
// Non standard browser env (web workers, react-native) lack needed support.
(function nonStandardBrowserEnv() {
return {
write: function write() {},
read: function read() { return null; },
remove: function remove() {}
};
})()
);
- 判断是否为标准浏览器,如果是,返回一个包含write,read,remove的对象
- write:处理cookie需要的参数,并且通过document.cookie设置
isAbsoluteURL
module.exports = function isAbsoluteURL(url) {
// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
// RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
// by any combination of letters, digits, plus, period, or hyphen.
return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
};
判断是否为相对路径的Url
parseHeaders
var ignoreDuplicateOf = [
'age', 'authorization', 'content-length', 'content-type', 'etag',
'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since',
'last-modified', 'location', 'max-forwards', 'proxy-authorization',
'referer', 'retry-after', 'user-agent'
];
module.exports = function parseHeaders(headers) {
var parsed = {};
var key;
var val;
var i;
if (!headers) { return parsed; }
utils.forEach(headers.split('\n'), function parser(line) {
i = line.indexOf(':');
key = utils.trim(line.substr(0, i)).toLowerCase();
val = utils.trim(line.substr(i + 1));
if (key) {
if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) {
return;
}
if (key === 'set-cookie') {
parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]);
} else {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
}
}
});
return parsed;
};
- headers为对象字符串,是通过getAllResponseHeaders获取的
- 处理key,val,并且key统一小写
- 如果parsed中存在key并且ignoreDuplicateOf中存在key,直接返回
- 如果key为set-cookie,则处理为数组,否则处理为字符串
spread
module.exports = function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};
可以配合axios.all使用,传入参数数组,调用callback
isURLSameOrigin
module.exports = (
utils.isStandardBrowserEnv() ?
// Standard browser envs have full support of the APIs needed to test
// whether the request URL is of the same origin as current location.
(function standardBrowserEnv() {
var msie = /(msie|trident)/i.test(navigator.userAgent);
var urlParsingNode = document.createElement('a');
var originURL;
/**
* Parse a URL to discover it's components
*
* @param {String} url The URL to be parsed
* @returns {Object}
*/
function resolveURL(url) {
var href = url;
if (msie) {
// IE needs attribute set twice to normalize properties
urlParsingNode.setAttribute('href', href);
href = urlParsingNode.href;
}
urlParsingNode.setAttribute('href', href);
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
return {
href: urlParsingNode.href,
protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
host: urlParsingNode.host,
search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
hostname: urlParsingNode.hostname,
port: urlParsingNode.port,
pathname: (urlParsingNode.pathname.charAt(0) === '/') ?
urlParsingNode.pathname :
'/' + urlParsingNode.pathname
};
}
originURL = resolveURL(window.location.href);
/**
* Determine if a URL shares the same origin as the current location
*
* @param {String} requestURL The URL to test
* @returns {boolean} True if URL shares the same origin, otherwise false
*/
return function isURLSameOrigin(requestURL) {
var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL;
return (parsed.protocol === originURL.protocol &&
parsed.host === originURL.host);
};
})() :
// Non standard browser envs (web workers, react-native) lack needed support.
(function nonStandardBrowserEnv() {
return function isURLSameOrigin() {
return true;
};
})()
);
判断是否为同源的URL:
- 首先判断是否为标准浏览器,如果不是简单处理
- 对于标准浏览器先格式化window.location.href,生成标准的url格式
- 格式化requestUrl,判断协议和host是否一致
总结
- axios分别处理了browser和node环境,browser使用了XMLHTTPRequest,node使用了http模块
- 通过default设置默认的方法参数,当用户没有设置对应参数时使用
- axios返回了一个实例,也是一个方法,方便直接调用request
- 维护调用栈chain,请求拦截器插入调用栈前面,响应拦截器拼接在数组后面,中间为请求体
- 支持all方法,实质为Promise的all
- 取消请求通过抛出cancel给用户