伍:axios1.x改动内容

前边我们对axios0.18.0的源码内容做了解读,但是0.18.0的版本有些老了,从github拉了axios1.0.0-beta版本来看一下有了哪些变动吧

目录结构与入口文件

目录结构与旧版本基本一致,只有些许变动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
lib	// 源码目录
|---- adapters // 适配器目录
| |---- index.js // ++++++++++++++++++++++++
| |---- xhr.js // 浏览器xhr
| |---- http.js // node环境http|
|---- cancel // 取消请求相关功能目录
| |---- CanceledError.js // ++++++++++++++++++
| |---- CancelToken.js
| |---- isCancel.js
|---- core // 核心代码目录
| |---- Axios.js // Axios类,用于创建axios实例
| |---- dispatchRequest.js // 封装发送请求的方法
| |---- InterceptorManager.js // 拦截器
| |---- settle.js
| |---- transformData.js // 数据转换
|---- helpers // 一些辅助函数
|---- defaults // 默认配置 修改为目录
|---- axios.js // 真正的入口文件
|---- utils.js // 一些公共方法

代码有了些或多或少的改动,位置也有了一些变化,语法也用上了ES6,但是内部的思想还是没有什么大的改动的。

axios.js

lib/axios.js加了几个新方法,但是更大的改动我觉得是createInstance这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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, {allOwnKeys: true});

// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};

return instance;
}

现在create方法是在craeteInstance方法添加到实例上的了,这也就意味着通过axios.creaet()创建的实例依然可以调用create方法,这是与老版本不同的。

axios还添加了一个有意思的方法formToJSON(),但是通过axios.create创建的实例依然不能调用CancelToken等用于取消请求的方法。

CancelToken

CancelToken的改动也挺大的,改用了listeners去收集需要取消的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
'use strict';

import CanceledError from './CanceledError.js';

/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @param {Function} executor The executor function.
*
* @returns {CancelToken}
*/
class CancelToken {
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}

let resolvePromise;

this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});

const token = this;

// eslint-disable-next-line func-names
this.promise.then(cancel => {
if (!token._listeners) return;

let i = token._listeners.length;

while (i-- > 0) {
token._listeners[i](cancel);
}
token._listeners = null;
});

// eslint-disable-next-line func-names
this.promise.then = onfulfilled => {
let _resolve;
// eslint-disable-next-line func-names
const promise = new Promise(resolve => {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);

promise.cancel = function reject() {
token.unsubscribe(_resolve);
};

return promise;
};

executor(function cancel(message, config, request) {
if (token.reason) {
// Cancellation has already been requested
return;
}

token.reason = new CanceledError(message, config, request);
resolvePromise(token.reason);
});
}

/**
* Throws a `CanceledError` if cancellation has been requested.
*/
throwIfRequested() {
if (this.reason) {
throw this.reason;
}
}

/**
* Subscribe to the cancel signal
*/

subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}

if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
}

/**
* Unsubscribe from the cancel signal
*/

unsubscribe(listener) {
if (!this._listeners) {
return;
}
const index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
}

/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
static source() {
let cancel;
const token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token,
cancel
};
}
}

export default CancelToken;

xhr

dispatchRequest基本是没有改动的,但是xhr适配器还是有一些改动的,取消请求的部分也随着CancelToken的变动有了不小的变动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
function progressEventReducer(listener, isDownloadStream) {
let bytesNotified = 0;
const _speedometer = speedometer(50, 250);

return e => {
const loaded = e.loaded;
const total = e.lengthComputable ? e.total : undefined;
const progressBytes = loaded - bytesNotified;
const rate = _speedometer(progressBytes);
const inRange = loaded <= total;

bytesNotified = loaded;

const data = {
loaded,
total,
progress: total ? (loaded / total) : undefined,
bytes: progressBytes,
rate: rate ? rate : undefined,
estimated: rate && total && inRange ? (total - loaded) / rate : undefined
};

data[isDownloadStream ? 'download' : 'upload'] = true;

listener(data);
};
}

export default function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
let requestData = config.data;
const requestHeaders = AxiosHeaders.from(config.headers).normalize();
const responseType = config.responseType;
let onCanceled;
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}

if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}

if (utils.isFormData(requestData) && utils.isStandardBrowserEnv()) {
requestHeaders.setContentType(false); // Let the browser set it
}

let request = new XMLHttpRequest();

// HTTP basic authentication
if (config.auth) {
const username = config.auth.username || '';
const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
}

const fullPath = buildFullPath(config.baseURL, config.url);

request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

// Set the request timeout in MS
request.timeout = config.timeout;

function onloadend() {
if (!request) {
return;
}
// Prepare the response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};

settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);

// Clean up request
request = null;
}

if ('onloadend' in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
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 onerror 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;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
setTimeout(onloadend);
};
}

// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort() {
if (!request) {
return;
}

reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));

// Clean up request
request = null;
};

// Handle low level network errors
request.onerror = function handleError() {
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));

// Clean up request
request = null;
};

// Handle timeout
request.ontimeout = function handleTimeout() {
let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
const transitional = config.transitional || transitionalDefaults;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(new AxiosError(
timeoutErrorMessage,
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
config,
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.
if (utils.isStandardBrowserEnv()) {
// Add xsrf header
const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
&& config.xsrfCookieName && cookies.read(config.xsrfCookieName);

if (xsrfValue) {
requestHeaders.set(config.xsrfHeaderName, xsrfValue);
}
}

// Remove Content-Type if data is undefined
requestData === undefined && requestHeaders.setContentType(null);

// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
request.setRequestHeader(key, val);
});
}

// Add withCredentials to request if needed
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}

// Add responseType to request if needed
if (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}

// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
}

// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
}

if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};

config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}

// false, 0 (zero number), and '' (empty string) are valid JSON values
if (!requestData && requestData !== false && requestData !== 0 && requestData !== '') {
requestData = null;
}

const protocol = parseProtocol(fullPath);

if (protocol && platform.protocols.indexOf(protocol) === -1) {
reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
return;
}


// Send the request
request.send(requestData);
});
}

本篇主要是把一些大的变动的内容给列出来了,没有对axios1.x对源码做解读。

至于会不会有axios1.x对源码解读还是看后续的时间吧

作者

胡兆磊

发布于

2022-10-27

更新于

2022-10-27

许可协议