写在开头
经过前两篇文章的学习,axios
的核心功能就讲完了,主要就是拦截器和取消请求这两个功能,剩下的就是一些细节性的东西,杂七杂八的了,今天我们就慢慢来把它给完善起来。
默认参数合并
在第二篇文章中,我们创建了一个 lib/defaults.js
文件,它是整个 axios
默认的配置信息文件。但我们还没具体使用到它,它记录的默认配置信息,最终肯定是要和我们使用 axios(config)
对象时传递的请求配置信息合并起来的,下面,我们且来瞧瞧在源码中,它是如何被使用到的。
先来到 lib/axios.js
文件:
var Axios = require('./core/Axios');
var bind = require('./helpers/bind');
var utils = require('./utils');
var defaults = require('./defaults'); // 引入默认配置文件
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig); // 实例化时接收默认配置
var instance = bind(Axios.prototype.request, context);
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
return instance;
}
var axios = createInstance(defaults); // 把默认配置传递到 Axios 实例中
axios.CancelToken = require('./cancel/CancelToken');
axios.Cancel = require('./cancel/Cancel');
axios.isCancel = require('./cancel/isCancel');
module.exports = axios;
复制代码
lib/core/Axios.js
文件:
var dispatchRequest = require('./dispatchRequest');
var InterceptorManager = require('./InterceptorManager');
var mergeConfig = require('./mergeConfig'); // 引入合并函数
function Axios(instanceConfig) {
this.defaults = instanceConfig; // 保存默认配置
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function request(config) { // config 为 请求配置, 这里说过很多遍啦。
// 允许 config 为字符串类型, 能直接传递一个 URL
// 例如: axios.get(url, config) ; 更多查看: https://www.axios-http.cn/docs/instance
if (typeof config === 'string') {
config = arguments[1] || {}; // 取第二个参数作为请求配置
config.url = arguments[0];
} else {
config = config || {};
}
// 默认配置 与 请求配置 进行合并
config = mergeConfig(this.defaults, config);
// 设置请求方式, 默认为 get, 都统一转成小写, 但在 xhr.js 中会统一转成大写
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
...
}
module.exports = Axios;
复制代码
新建 lib/core/mergeConfig.js
文件:
var utils = require('../utils');
/**
* 把默认配置和请求配置合并后返回, 以请求配置为准
* @param {*} config1: 默认配置
* @param {*} config2: 请求配置
*/
module.exports = function mergeConfig(config1, config2 = {}) {
var config = {};
// 规定一些 "特定属性", 按 它们值的类型 或者 它们的功能 分类好
var valueFromConfig2Keys = ['url', 'method', 'data'];
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params'];
var defaultToConfig2Keys = [
'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer',
'timeout', 'timeoutMessage', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'decompress',
'maxContentLength', 'maxBodyLength', 'maxRedirects', 'transport', 'httpAgent',
'httpsAgent', 'cancelToken', 'socketPath', 'responseEncoding'
];
var directMergeKeys = ['validateStatus'];
// 合并两个值
function getMergedValue(target, source) {
if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
// 如果两个值都是普通对象, 则直接合并
return utils.merge(target, source);
} else if (utils.isPlainObject(source)) {
// 如果第一个值不是一个普通对象, 第二个值是一个普通对象, 则直接返回第二个值
return utils.merge({}, source);
} else if (utils.isArray(source)) {
// 如果两个值都不是普通对象, 且第二个值是一个数组, 则返回一个新数组对象
return source.slice();
}
// 如果都不是就返回第二个值
return source;
}
// 针对对象形式
function mergeDeepProperties(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(config1[prop], config2[prop]);
} else if (!utils.isUndefined(config1[prop])) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
}
// 这四个 forEach 是那些 "特定属性" 的合并具体过程
utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
if (!utils.isUndefined(config2[prop])) { // 请求配置中存在这个属性
config[prop] = getMergedValue(undefined, config2[prop]);
}
});
utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties);
utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(undefined, config2[prop]);
} else if (!utils.isUndefined(config1[prop])) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
});
utils.forEach(directMergeKeys, function merge(prop) {
if (prop in config2) {
config[prop] = getMergedValue(config1[prop], config2[prop]);
} else if (prop in config1) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
});
// 把所有 "特定属性" 拼接成一个数组
var axiosKeys = valueFromConfig2Keys
.concat(mergeDeepPropertiesKeys).concat(defaultToConfig2Keys).concat(directMergeKeys);
// 找到 请求配置 中不属于 "特定属性" 的那些key, 像 axios({url: '', name: '橙某人'}) 中的 name
var otherKeys = Object.keys(config1).concat(Object.keys(config2)).filter(function filterAxiosKeys(key) {
return axiosKeys.indexOf(key) === -1;
});
// 直接把不属于 "特定属性" 的其他属性做一个拷贝
utils.forEach(otherKeys, mergeDeepProperties);
return config;
}
复制代码
默认配置 和 请求配置 的合并其实就是简单的两个对象合并过程,不过,由上面的 mergeConfig.js
文件可以看出,它也不是一股脑直接就用 Object.assign()
合并完就算了,它是有条件的筛选规定的属性进行合并,并且是以 请求配置 的对象为准的。
那么,写到这里 默认配置 和 请求配置 的合并就完成,在 lib/core/Axios.js
文件中,我们即可获取到一个完整的参数信息了。
接下来,我们继续哈。(✪ω✪)
构建响应结果
正常情况下,我们使用 axios
发送一个请求,axios
会把我们的响应结果做一层封装,把后端真正返回的数据塞在 data
属性下,还会多返回 config
、headers
、status
等属性其他信息。
下面,我们一起来看看源码中是如何做的。
来到 lib/adapters/xhr.js
文件:
var utils = require('./../utils');
var settle = require('./../core/settle');
var parseHeaders = require('./../helpers/parseHeaders');
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 这里的config已经是将默认配置和请求配置合并后的结果了
var requestData = config.data; // 获取到config.data中的请求数据, config.params 我们后续再讲
var requestHeaders = config.headers;
var responseType = config.responseType;
var request = new XMLHttpRequest();
request.open(config.method.toUpperCase(), config.url, true);
// 设置超时时长: 毫秒, 超时请求会自动终止
request.timeout = config.timeout;
// 定义一个请求结束后的处理函数
function onloadend() {
if (!request) return;
// XMLHttpRequest.getAllResponseHeaders() 会返回所有响应头, 以字符串的形式, 详情可以看这里: https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/getAllResponseHeaders
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
// 根据响应类型获取返回结果
var responseData = !responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response;
// 构建响应结果
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
// 验证响应结果状态码情况
settle(resolve, reject, response);
// 请求结束, 清空当前的 XMLHttpRequest 对象
request = null;
}
if ('onloadend' in request) {
request.onloadend = onloadend;
} else {
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) return;
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) return;
// 这里制造出下一个宏任务的原因是: readystate处理程序是在ontimeout或onerror处理程序之前调用, 我们应该要在它们调用结束之后才来统一调用onloadend处理最终结果
setTimeout(onloadend);
};
}
// 监听超时
request.ontimeout = function handleTimeout() {
reject('请求超时');
request = null;
};
// 监听网络错误
request.onerror = function handleError() {
reject('网络错误');
request = null;
};
if (config.cancelToken) {
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) return;
request.abort();
reject(cancel);
request = null;
});
}
request.send(requestData); // XMLHttpRequest请求 请求中要发送的数据体:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/send
});
}
复制代码
我们新建 lib/core/settle.js
文件:
// 验证响应结果的状态值, 再去调用 resolve 或者 reject
module.exports = function settle(resolve, reject, response) {
// 可以通过 config.validateStatus 自定义响应状态码的验证规则, 默认值: status >= 200 && status < 300
var validateStatus = response.config.validateStatus;
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject('出现错误了');
}
};
复制代码
settle.js
文件的作用就是对外提供一个自定义验证 HTTP
状态码的功能,像文档上说的这样子:
再新建 lib/helpers/parseHeaders.js
文件:
var utils = require('./../utils');
// Headers whose duplicates are ignored by node
// c.f. https://nodejs.org/api/http.html#http_message_headers
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;
};
复制代码
这个文件的作用是把返回的响应头字符串转变成对象的形式。因为请求结束后,返回的响应头是以字符串的形式存在的,为了方便我们查看,需要进行转换,而我们可以通过 XMLHttpRequest.getAllResponseHeaders() 方法获取到所有的响应头字符串。
做完以上这些,我们执行 grunt build
命令把项目打包,然后写个测试用例看看:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/axios.js"></script>
</head>
<body>
<script>
axios({
url: 'http://localhost:3000/posts',
method: 'get',
}).then(res => {
console.log(res)
})
</script>
</body>
</html>
复制代码
可以看到 axios
的响应结果我们大致构建出来了,但是图中,我们的 data
属性中的数据还只是一个 json
字符串,这还需要一些后续的工作,我们继续来看囖。(✪ω✪)
transformResponse
上面我们一直在说默认配置 lib/defaults.js
文件,但我们之前在 第二篇 文章中也只是给它写了一点内容而已:
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// adapter = require('./adapters/http');
}
return adapter;
}
var defaults = {
timeout: 0,
adapter: getDefaultAdapter(),
}
module.exports = defaults;
复制代码
现在,我们来把它完善完善 lib/defaults.js
文件:
var utils = require('./utils');
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// adapter = require('./adapters/http');
}
return adapter;
}
var defaults = {
timeout: 0, // 超时时长
adapter: getDefaultAdapter(), // 适配器
// 在 lib/core/settle.js 中用于默认验证 HTTP 状态码
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
},
transitional: {
silentJSONParsing: true, // 版本兼容配置-返回值转换为 Json 出错时是否置为 null 返回
forcedJSONParsing: true, // 版本兼容配置-responseType 设置非 json 类型时是否强制转换成 json 格式
clarifyTimeoutError: false, // 版本兼容配置-请求超时时是否默认返回 ETIMEDOUT 类型错
},
// 注意 transformResponse 属性设计成一个数组, 是因为能更加方便的被使用, 我们可以通过传递数组,并通过一个一个函数来改造响应结果, 这样无疑是多样化的
transformResponse: [function transformResponse(data) {
var transitional = this.transitional;
var silentJSONParsing = transitional && transitional.silentJSONParsing;
var forcedJSONParsing = transitional && transitional.forcedJSONParsing;
var strictJSONParsing = !silentJSONParsing && this.responseType === 'json';
if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) {
try {
// 把数据转换成对象, 因为 xhr.js 文件中的取的是 responseText
return JSON.parse(data);
} catch (e) {
if (strictJSONParsing) {
if (e.name === 'SyntaxError') {
// throw enhanceError(e, this, 'E_JSON_PARSE');
}
throw e;
}
}
}
return data;
}],
}
module.exports = defaults;
复制代码
上面代码中,我们添加了一些默认配置,但我们主要是来看 transformResponse
属性,或许有一些小伙伴对它还比较陌生,下面是文档对它的介绍,可以先看看。其实它应该还有一个"孪生兄弟" - transformRequest
属性,但是我们暂时用不上,现在只是想解决 data
是字符串的问题,后面我们再来说它。
添加完默认配置后,我们来到 lib/core/dispatchRequest.js
文件:
var defaults = require('../defaults');
var isCancel = require('../cancel/isCancel');
var transformData = require('./transformData'); // 引入新文件
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
// 这个 config 是默认配置和请求配置合并后的结果
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// 返回响应结果前, 去执行 transformData() 方法, 其实也就是去执行 transformResponse 数组,
// transformData() 方法内部就是遍历 transformResponse 数组依次执行每个函数
response.data = transformData.call(
config, // transformResponse数组中每个函数的 this
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
}
return Promise.reject(reason);
});
}
复制代码
新建 lib/core/transformData.js
文件:
var utils = require('./../utils');
var defaults = require('./../defaults');
module.exports = function transformData(data, headers, fns) {
// 使用这个方法时, this 可以通过 call() 等方法去设置, 否则就取 默认配置 对象为this
var context = this || defaults;
utils.forEach(fns, function transform(fn) {
// 执行每个函数, 把data和headers作为函数参数, 并改变函数内部this
data = fn.call(context, data, headers);
});
return data;
};
复制代码
做完上面的工作后,我们就能进行测试了,执行 grunt build
命令,还是上面那个例子:
可以看到 data
属性中的结果变成正常的对象形式了。
当然,还没完,我们顺便测试一下 transformResponse
的功能:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/axios.js"></script>
</head>
<body>
<script>
axios({
url: 'http://localhost:3000/posts',
method: 'get',
transformResponse: [(data, headers) => {
headers.name = '橙某人';
return data;
}, (data, headers) => {
console.log(data, headers)
return data;
}],
}).then(res => {
console.log(res)
})
</script>
</body>
</html>
复制代码
上面例子中,我改动了一下 headers
参数,在其中添加了一个 name
属性,而 data
参数我没动,所以最终它的值还是一个字符串数组。
这说明了一个问题,当我们请求配置中提供了 transformResponse
属性时,就只会执行请求配置中的 transformResponse
属性了,而当我们没有提供时, lib/defaults.js
文件中默认配置的 transformResponse
属性就会被执行。
一切如我们所料,真是太棒了呢,是吧o(^▽^)o。
transformRequest
完成了 transformResponse
属性后,我们来看看它的"孪生兄弟" - transformRequest
属性。
同样在 lib/defaults.js
文件中先添加默认配置:
var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName'); // 引入新文件
function getDefaultAdapter() {
...
}
// 设置请求头
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
var defaults = {
...,
// 关于请求头信息: https://developer.mozilla.org/zh-CN/docs/Glossary/Request_header
headers: {
// 默认必传请求头
common: {
'Accept': 'application/json, text/plain, */*'
}
},
transformRequest: [function transformRequest(data, headers) {
// 验证名称大小写
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data)
|| utils.isStream(data) || utils.isFile(data) || utils.isBlob(data)) {
return data;
}
if (utils.isArrayBufferView(data)) return data.buffer;
if (utils.isURLSearchParams(data)) {
// 如果请求的参数data是一个 URLSearchParams 对象, 则会自动设置特殊的请求头
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) {
setContentTypeIfUnset(headers, 'application/json');
return JSON.stringify(data);
}
return data;
}],
}
// 每一种不同类型的请求,可能会有一些不一样的请求头, 所以都给他们一个对象,方便我们为它们各自设置一些不一样的默认请求头。
// 如下图
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
module.exports = defaults;
复制代码
老样子,我们回到 lib/core/dispatchRequest.js
文件:
var defaults = require('../defaults');
var isCancel = require('../cancel/isCancel');
var transformData = require('./transformData');
var utils = require('./../utils'); // 引入新文件
function throwIfCancellationRequested(config) {
...
}
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// 确保 headers 属性一定是一个对象, 因为如果 请求配置 中把 axios({headers: undefined}), 后续会带来不必要的麻烦
config.headers = config.headers || {};
// 请求发送前, 对象请求参数data做处理, 同transformResponse原理过程一样
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
// 合并 headers, 以 config.headers 为准
config.headers = utils.merge(
config.headers.common || {}, // 默认必传请求头
config.headers[config.method] || {}, // 当前请求类型 的其他一些 默认请求头
config.headers // 请求配置 中的请求头
);
// 删除默认请求了, 保持 header 的纯净
utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
response.data = transformData.call(
config, // transformResponse数组中每个函数的 this
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
}
return Promise.reject(reason);
});
}
复制代码
修改完后,我们先把项目打包 grunt build
,然后继续写个例子来测试一下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/axios.js"></script>
</head>
<body>
<script>
axios({
url: 'http://localhost:3000/posts',
method: 'get',
}).then(res => {
console.log(res)
})
</script>
</body>
</html>
复制代码
观察图中的结果后,可以发现我们的默认请求头并没有生效。
这是因为还差那么一小步唷,继续来看,修改 lib/adapters/xhr.js
文件:
var utils = require('./../utils');
var settle = require('./../core/settle');
var parseHeaders = require('./../helpers/parseHeaders');
var utils = require('./../utils'); // 引入新文件
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.responseType;
// 如果传递的 data 为 FormData 对象类型, 则删除头部的设置, 浏览器会自动为 FormData 参数类型的请求设置请求头
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type'];
}
var request = new XMLHttpRequest();
request.open(config.method.toUpperCase(), config.url, true);
request.timeout = config.timeout;
function onloadend() {
if (!request) return;
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
request = null;
}
if ('onloadend' in request) {
request.onloadend = onloadend;
} else {
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) return;
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) return;
setTimeout(onloadend);
};
}
// 设置请求头,
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// 如果没有设置 config.data 则不需要 content-type 请求头
delete requestHeaders[key];
} else {
request.setRequestHeader(key, val);
}
});
}
request.ontimeout = function handleTimeout() {};
request.onerror = function handleError() {};
if (config.cancelToken) {}
request.send(requestData);
});
}
复制代码
上面代码中,我们添加了两处代码,其实应该很简单啦,不就是把 headers
对象转化到 XMLHttpRequest
对象身上囖,其中主要是利用了 XMLHttpRequest.setRequestHeader() 方法来完成。
我们再次打包(grunt build
)测试:
可以发现,这次就真的成功了。
当然,还没完,我们还得继续测试一下 transformRequest
属性与 headers
属性的具体使用功能呢。
我们先来补充一下关于 json-server
模块中 post
方法的使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<script>
axios({
url: 'http://localhost:3000/posts',
method: 'post',
data: {
title: '测试',
author: "测试人员"
}
}).then(res => {
console.log(res)
})
</script>
</body>
</html>
复制代码
我们引入了正常的 axios
库,然后发送一个 post
请求,这个时候,你来查看你启动的 xxx.json
文件(第一篇文章讲过 json-server
模块的使用),会发送 post
请求的数据被插入进来了,这是关于 json-server
模块中 post
方式的使用。
知道了 post
请求方式后,我们就能来测试,本来我们按道理来说,估计会怎么写的吧:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/axios.js"></script>
</head>
<body>
<script>
axios({
url: 'http://localhost:3000/posts',
method: 'post',
data: {
title: '测试',
author: "测试人员"
},
transformRequest: [(data) => {
data.author = '橙某人';
return data;
}],
headers: {
anth: '11' // 传递其他请求头, 注意值不能是中文, 不要问我为什么知道...
}
}).then(res => {
console.log(res)
})
</script>
</body>
</html>
复制代码
对于 headers
属性,它能传递其他请求头说明它的功能应该是正常的了。
而对于 transformRequest
属性,当我看到这两个结果后,我懵逼了!!!
经过测试,我发现原本 axios
也是有这个问题,这就应该不是在重写源码的时候写错导致的了。
仔细看了一下请求头,发现 Content-Type
竟然是以表单的形式在提交的。
这其实是在源码中 lib/defaults.js
文件写的默认配置:
好,事情真相大白了,不关我的事,果断甩锅。
而之所以 axios
会这么设计,这里小编的个人想法是,当我们使用 transformRequest
属性的时候,很多时候是想改变整个请求参数的类型,比如序列化参数这种。
在小编的另一篇 完整的Axios封装-单独API管理层、参数序列化、取消重复请求、Loading、状态码... 文章里面写到的这种序列化:
utils.js
你可以看小编写的 utils.js
文件,标有一些中午注释,或者直接看 官方utils.js 文件。
var bind = require('./helpers/bind');
var toString = Object.prototype.toString;
/**
* 判断是否是一个字符串
* @param {Object} val
* @returns {boolean}
*/
function isString(val) {
return typeof val === 'string';
}
/**
* 判断是否是 undefined
* @param {Object} val
* @returns {boolean}
*/
function isUndefined(val) {
return typeof val === 'undefined';
}
/**
* 判断是否是对象
* @param {Object} val
* @returns {boolean}
*/
function isObject(val) {
return val !== null && typeof val === 'object';
}
/**
* 判断是否是一个函数
* @param {Object} val
* @returns {boolean}
*/
function isFunction(val) {
return toString.call(val) === '[object Function]';
}
/**
* 判断对象是否是普通对象
* @param {Object} val
* @return {boolean}
*/
function isPlainObject(val) {
if (toString.call(val) !== '[object Object]') {
return false;
}
var prototype = Object.getPrototypeOf(val);
return prototype === null || prototype === Object.prototype;
}
/**
* 判断是否是数组
* @param {Object} val
* @returns {boolean}
*/
function isArray(val) {
return toString.call(val) === '[object Array]';
}
/**
* 判断是否可以使用 URLSearchParams, 浏览器和Node环境均可使用, 但低版本浏览器要使用需要安装 npm install --save url-search-params
* URLSearchParams 能帮助你快速解析URL中的参数请求, 也能帮助你快速拼接URL中需要的参数形式
* @param {Object} val
* @returns {boolean}
*/
function isURLSearchParams(val) {
return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
}
/**
* 判断是否是日期类型
* @param {Object} val
* @returns {boolean}
*/
function isDate(val) {
return toString.call(val) === '[object Date]';
}
/**
* 判断是否是 FormData 对象
* @param {Object} val
* @returns {boolean}
*/
function isFormData(val) {
return (typeof FormData !== 'undefined') && (val instanceof FormData);
}
/**
* 判断是否是一个 ArrayBuffer 类型
* @param {Object} val
* @returns {boolean}
*/
function isArrayBuffer(val) {
return toString.call(val) === '[object ArrayBuffer]';
}
/**
* 判断是否是一个 Buffer 类型
* @param {Object} val
* @returns {boolean}
*/
function isBuffer(val) {
return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
&& typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}
/**
* 判断是否是一个 Stream 类型
* @param {Object} val
* @returns {boolean}
*/
function isStream(val) {
return isObject(val) && isFunction(val.pipe);
}
/**
* 判断是否是一个 File 类型
* @param {Object} val
* @returns {boolean}
*/
function isFile(val) {
return toString.call(val) === '[object File]';
}
/**
* 判断是否是一个 Blob 类型
* @param {Object} val
* @returns {boolean}
*/
function isBlob(val) {
return toString.call(val) === '[object Blob]';
}
/**
* 判断是否是一个 isArrayBufferView
* @param {Object} val
* @returns {boolean}
*/
function isArrayBufferView(val) {
var result;
if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
result = ArrayBuffer.isView(val);
} else {
result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer);
}
return result;
}
/**
* 去除头尾空格
* @param {String} str
* @returns {String}
*/
function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}
/**
* 重写 forEach
* @param {Object|Array} obj
* @param {Function} fn
* @returns
*/
function forEach(obj, fn) {
if (obj === null || typeof obj === 'undefined') return;
// 不是对象类型, 则变成一个数组, 如函数
if (typeof obj !== 'object') obj = [obj];
if (isArray(obj)) {
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
/**
* 扩展对象 a 身上的属性, 会将 b 身上的属性拷贝到 a 身上
* @param {Object} a
* @param {Object} b
* @param {Object} thisArg: this 指向
*/
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
/**
* 接收多个对象进行合并
* @param {Object} obj1
* @returns {Object}
*/
function merge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (isPlainObject(result[key]) && isPlainObject(val)) {
result[key] = merge(result[key], val);
} else if (isPlainObject(val)) {
result[key] = merge({}, val);
} else if (isArray(val)) {
result[key] = val.slice();
} else {
result[key] = val;
}
}
for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}
module.exports = {
isString: isString,
isUndefined: isUndefined,
isArray: isArray,
isObject: isObject,
isPlainObject: isPlainObject,
isDate: isDate,
isFunction: isFunction,
isFormData: isFormData,
isArrayBuffer: isArrayBuffer,
isBuffer: isBuffer,
isStream: isStream,
isFile: isFile,
isBlob: isBlob,
isArrayBufferView: isArrayBufferView,
isURLSearchParams: isURLSearchParams,
trim: trim,
forEach: forEach,
extend: extend,
merge: merge
}
复制代码
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。