• axios是如何实现的(源码解析)


    1 axios的实例与请求流程

    在阅读源码之前,先大概了解一下axios实例的属性和请求整体流程,带着这些概念,阅读源码可以轻松不少!

    下图是axios实例属性的简图。

    可以看到axios的实例上,其实主要就这三个东西:

    1. config:配置,比如url、method、params、headers等等
    2. interceptors :拦截器,分为请求拦截器和返回拦截器。
    3. request:调用xhr或者http请求的方法,参数就是config

    由于调用request方法的时候可以再次传入config,但是不能传入interceptors,所以拦截器一定是要在请求之前就在axios上添加好,不能临时加。

    下图是axios的请求流程,其实相当简单,先了解这个流程,看源码的时候就会有方向。

    2 源码文件结构解析

    axios的源码都在lib文件夹下,最核心的内容在core文件夹里。

    lib│  axios.js    // 最终导出的文件│  utils.js    // 工具类├─adapters     // 适配器相关│      adapters.js //适配器类│      http.js     // node请求│      xhr.js      // 浏览器请求├─cancel      // 取消功能相关│      CanceledError.js //取消异常类│      CancelToken.js   //取消token类│      isCancel.js      //判断是否取消├─core       // 核心功能相关 │      Axios.js                 // axios类│      AxiosError.js            // axios异常类│      AxiosHeaders.js          // 请求头│      buildFullPath.js         // 构造请求地址│      dispatchRequest.js       // 发送请求方法│      InterceptorManager.js    // 拦截器的类│      mergeConfig.js           // 合并配置方法│      settle.js                // 处理请求结果方法│      transformData.js         // 数据转换执行方法├─defaults    // 默认配置index.js                 // 默认请求参数配置│      transitional.js          // 默认transitional配置├─env        //  node环境没有FormData,│  │  data.js│  └─classes│          FormData.js├─helpers  // 各种工具类方法,看名字就可以大概猜到作用│      AxiosTransformStream.js│      AxiosURLSearchParams.js│      bind.js│      buildURL.js│      callbackify.js│      combineURLs.js│      cookies.js│      deprecatedMethod.js│      formDataToJSON.js│      formDataToStream.js│      fromDataURI.js│      HttpStatusCode.js│      isAbsoluteURL.js│      isAxiosError.js│      isURLSameOrigin.jsnull.js│      parseHeaders.js│      parseProtocol.js│      readBlob.js│      README.md│      speedometer.js│      spread.js│      throttle.js│      toFormData.js│      toURLEncodedForm.js│      validator.js│      ZlibHeaderTransformStream.js└─platform  // 为不同环境下准备的方法index.js    ├─browser    │  │  index.js    │  └─classes    │          Blob.js    │          FormData.js    │          URLSearchParams.js    └─nodeindex.js        └─classes                FormData.js                URLSearchParams.js

    3 源码文件阅读

    3.1 入口文件 axios.js

    该文件创建了一个axios实例,并且导出,所以我们import axios from 'axios'引入的就是该实例,可以直接使用,不需要再new Axios({...})这样写。

    下面看一下axios实例是如何创建的吧~

    1. // 核心方法,根据config创建axios实例
    2. function createInstance (defaultConfig) {
    3. // 创建axios实例
    4. const context = new Axios(defaultConfig);
    5. // 给Axios原型上的request方法绑定context为它的this
    6. // 这个instance就是我们最终使用的axios
    7. // 没想到吧,最开始的instance其实是个函数,
    8. // 所以我们才可以使用这个用法axios('/api/url')
    9. // 只不过后面给它扩展了很多东西
    10. const instance = bind(Axios.prototype.request, context);
    11. // 将Axios.prototype上的属性都绑定到instance上,
    12. // 这样它就拥有了简写的请求方法,比如axios.get(),axios.post()
    13. // 如果是函数,this绑定为context
    14. utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });
    15. // 将context上的属性都绑定到instance上,
    16. // 这样它就拥有了拦截器属性,可以使用axios.interceptors.request.use()
    17. // 因为context上的函数的this本就指向context,所以第三个参数不需要再指定
    18. utils.extend(instance, context, null, { allOwnKeys: true });
    19. // 给instance增加create方法,可以通过create创建一个实例
    20. instance.create = function create (instanceConfig) {
    21. // 入参为拼接配置项,以instanceConfig为优先
    22. return createInstance(mergeConfig(defaultConfig, instanceConfig));
    23. };
    24. return instance;
    25. }
    26. // 调用上面的方法,最终导出的是axios,
    27. // 其实是Axios.prototype.request,并扩展了很多属性
    28. const axios = createInstance(defaults);
    29. // 继续给axios增加属性
    30. // 这就说明如果自己通过const myAxios=axios.create({});
    31. // 创建出来的实例就没有下面这些属性了
    32. // 所以下面这些属性只能通过import axios from 'axios';
    33. // axios.all()这样的方式来使用
    34. axios.Axios = Axios;
    35. // Cancel相关
    36. axios.CanceledError = CanceledError;
    37. axios.CancelToken = CancelToken;
    38. axios.isCancel = isCancel;
    39. axios.VERSION = VERSION;
    40. // 工具函数,将对象转为FormData
    41. axios.toFormData = toFormData;
    42. // Axios通用异常类
    43. axios.AxiosError = AxiosError;
    44. // Cancel异常类
    45. axios.Cancel = axios.CanceledError;
    46. // Expose all/spread
    47. // 工具函数
    48. axios.all = function all (promises) {
    49. return Promise.all(promises);
    50. };
    51. // 工具函数,利用apply将数组参数改为单个传入的参数
    52. axios.spread = spread;
    53. // 判断异常是否是AxiosError
    54. axios.isAxiosError = isAxiosError;
    55. // 合并config对象方法
    56. axios.mergeConfig = mergeConfig;
    57. axios.AxiosHeaders = AxiosHeaders;
    58. // 工具方法
    59. axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
    60. // 获取适配器:xhr 、http
    61. axios.getAdapter = adapters.getAdapter;
    62. // 请求状态
    63. axios.HttpStatusCode = HttpStatusCode;
    64. axios.default = axios;
    65. // 最终导出
    66. export default axios

    3.2 Axios类

    从上面的文件可以看出,axios扩展了Axios的原型方法和Axios实例的属性,所以接下来要重点看Axios的类里有什么内容。

    1. class Axios {
    2. // 可以看到Axios的构造函数相当简单
    3. // 仅仅是保存了我们传入的config,
    4. // 然后初始化空的拦截器对象
    5. constructor(instanceConfig) {
    6. // 所有的配置都设置再defaults上
    7. this.defaults = instanceConfig;
    8. // 初始化空的拦截器对象,包含请求拦截器request和返回拦截器response
    9. this.interceptors = {
    10. request: new InterceptorManager(),
    11. response: new InterceptorManager()
    12. };
    13. }
    14. // request是Axios的核心方法
    15. // 所有的核心都在request方法里,
    16. // request方法接收两种参数,【直接传config对象】或者【传url和config对象】
    17. request (configOrUrl, config) {
    18. // 允许axios('example/url'[, config]) 这样使用
    19. if (typeof configOrUrl === 'string') {
    20. config = config || {};
    21. config.url = configOrUrl;
    22. } else {
    23. config = configOrUrl || {};
    24. }
    25. // request会使用传入的配置merge默认配置
    26. // 所以即使只传了一个url,也会使用默认的Get方法
    27. config = mergeConfig(this.defaults, config);
    28. const { headers } = config;
    29. // 默认get请求
    30. config.method = (config.method || this.defaults.method || 'get').toLowerCase();
    31. // 说明header可以直接设置
    32. // 也可以在common设置通用header,也可以为每种请求设置特定的header
    33. let contextHeaders = headers && utils.merge(
    34. headers.common,
    35. headers[config.method]
    36. );
    37. headers && utils.forEach(
    38. ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    39. (method) => {
    40. delete headers[method];
    41. }
    42. );
    43. // 优先使用headers下配置,再使用headers.common和headers[get,post]的配置
    44. config.headers = AxiosHeaders.concat(contextHeaders, headers);
    45. // 请求拦截器链
    46. const requestInterceptorChain = [];
    47. // 记录是否使用同步的方式调用,我们配置拦截器的时候,默认是false,也就是异步
    48. let synchronousRequestInterceptors = true;
    49. this.interceptors.request.forEach(function unshiftRequestInterceptors (interceptor) {
    50. // 如果配置了runWhen函数,那么会先执行runWhen,如果为true,才会添加该拦截器
    51. if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
    52. return;
    53. }
    54. synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
    55. // unshift说明后传入的请求拦截器先执行,一次放入两个,分别是fulfilled和rejected
    56. requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    57. });
    58. // 响应拦截器链
    59. const responseInterceptorChain = [];
    60. this.interceptors.response.forEach(function pushResponseInterceptors (interceptor) {
    61. // push说明先传入的响应拦截器先执行
    62. responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    63. });
    64. let promise;
    65. let i = 0;
    66. let len;
    67. // 默认是异步执行,也就是一个执行完再执行下一个
    68. if (!synchronousRequestInterceptors) {
    69. //dispatchRequest是真正的发送请求
    70. const chain = [dispatchRequest.bind(this), undefined];
    71. // 前面插入请求拦截器
    72. chain.unshift.apply(chain, requestInterceptorChain);
    73. // 后面插入响应拦截器
    74. chain.push.apply(chain, responseInterceptorChain);
    75. len = chain.length;
    76. promise = Promise.resolve(config);
    77. // 依次执行
    78. while (i < len) {
    79. promise = promise.then(chain[i++], chain[i++]);
    80. }
    81. return promise;
    82. }
    83. len = requestInterceptorChain.length;
    84. let newConfig = config;
    85. i = 0;
    86. // 同步执行,请求拦截器
    87. while (i < len) {
    88. const onFulfilled = requestInterceptorChain[i++];
    89. const onRejected = requestInterceptorChain[i++];
    90. try {
    91. newConfig = onFulfilled(newConfig);
    92. } catch (error) {
    93. onRejected.call(this, error);
    94. break;
    95. }
    96. }
    97. // 发起请求
    98. try {
    99. promise = dispatchRequest.call(this, newConfig);
    100. } catch (error) {
    101. return Promise.reject(error);
    102. }
    103. i = 0;
    104. len = responseInterceptorChain.length;
    105. // 返回有异常可以继续走下去
    106. while (i < len) {
    107. promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
    108. }
    109. return promise;
    110. }
    111. // 获取请求地址
    112. getUri (config) {
    113. config = mergeConfig(this.defaults, config);
    114. const fullPath = buildFullPath(config.baseURL, config.url);
    115. return buildURL(fullPath, config.params, config.paramsSerializer);
    116. }
    117. }
    118. // Provide aliases for supported request methods
    119. // 给Axios原型注入四个请求方法,请求方法本质都是调用request方法
    120. // 这四个都是不带请求体的
    121. utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData (method) {
    122. Axios.prototype[method] = function (url, config) {
    123. return this.request(mergeConfig(config || {}, {
    124. method,
    125. url,
    126. data: (config || {}).data
    127. }));
    128. };
    129. });
    130. // 给Axios注入post,put,patch,postForm,putForm,patchForm方法
    131. // 这几个方法都是带请求体的
    132. utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData (method) {
    133. function generateHTTPMethod (isForm) {
    134. return function httpMethod (url, data, config) {
    135. return this.request(mergeConfig(config || {}, {
    136. method,
    137. headers: isForm ? {
    138. 'Content-Type': 'multipart/form-data'
    139. } : {},
    140. url,
    141. data
    142. }));
    143. };
    144. }
    145. Axios.prototype[method] = generateHTTPMethod();
    146. Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
    147. });
    148. export default Axios;

    3.3 InterceptorManager类

    接下来看看拦截器是如何实现的。

    先回顾一下我们平时是怎么使用拦截器的?

    1. axios.interceptors.request.use({
    2. fulfilled:()=>{},
    3. rejected:()=>{}
    4. })

    可以看到我们给use传递的是一个对象,对象包含fulfilled函数和rejected函数。

    接下来看源码:

    1. class InterceptorManager {
    2. // 构造函数只初始化了一个空的handlers数组
    3. // 拦截器就是放在这个数组里的
    4. constructor() {
    5. this.handlers = [];
    6. }
    7. // 添加拦截器,返回索引,可以用索引来移除拦截器
    8. // 可以发现除了fulfilled和rejected,
    9. // 我们还可以设置synchronous和runWhen
    10. // runWhen函数用来动态控制是否使用该拦截器
    11. use (fulfilled, rejected, options) {
    12. this.handlers.push({
    13. fulfilled,
    14. rejected,
    15. synchronous: options ? options.synchronous : false,
    16. runWhen: options ? options.runWhen : null
    17. });
    18. return this.handlers.length - 1;
    19. }
    20. // 根据添加时返回的索引去删除拦截器
    21. eject (id) {
    22. if (this.handlers[id]) {
    23. this.handlers[id] = null;
    24. }
    25. }
    26. // 清空拦截器
    27. clear () {
    28. if (this.handlers) {
    29. this.handlers = [];
    30. }
    31. }
    32. // 提供遍历拦截器快捷操作
    33. forEach (fn) {
    34. utils.forEach(this.handlers, function forEachHandler (h) {
    35. if (h !== null) {
    36. fn(h);
    37. }
    38. });
    39. }
    40. }
    41. export default InterceptorManager;

    3.4 dispatchRequest发送请求

    看完上面的代码,我们已经基本搞清楚了axios的整体流程:

    组装config->组装header->调用请求拦截器->发送实际请求->调用返回拦截器。

    但是我们还不知道axios具体是如何调用请求的,那么接下来就要看dispatchRequest代码咯!

    1. // 暂且先记住,这个函数的作用就是用来判断请求是否被取消,
    2. // 如果要的话,则直接抛出异常,
    3. function throwIfCancellationRequested (config) {
    4. if (config.cancelToken) {
    5. config.cancelToken.throwIfRequested();
    6. }
    7. if (config.signal && config.signal.aborted) {
    8. throw new CanceledError(null, config);
    9. }
    10. }
    11. // 发送请求核心函数
    12. export default function dispatchRequest (config) {
    13. // 刚开始请求前判断一次是否取消
    14. throwIfCancellationRequested(config);
    15. config.headers = AxiosHeaders.from(config.headers);
    16. // 执行数据转换操作
    17. config.data = transformData.call(
    18. config,
    19. config.transformRequest
    20. );
    21. // 默认设置请求头的contentType为application/x-www-form-urlencoded
    22. if (['post', 'put', 'patch'].indexOf(config.method) !== -1) {
    23. config.headers.setContentType('application/x-www-form-urlencoded', false);
    24. }
    25. // 获取适配器,如果是浏览器环境获取xhr,
    26. // 如果是Node环境,获取http
    27. // 适配器就是最终用来发送请求的东西
    28. const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
    29. // 请求是使用适配器执行config
    30. return adapter(config).then(function onAdapterResolution (response) {
    31. // 请求完之后判断是否要取消
    32. throwIfCancellationRequested(config);
    33. // 对返回结果进行转换
    34. response.data = transformData.call(
    35. config,
    36. config.transformResponse,
    37. response
    38. );
    39. // 设置返回头
    40. response.headers = AxiosHeaders.from(response.headers);
    41. return response;
    42. }, function onAdapterRejection (reason) {
    43. // 如果不是因为取消而报错
    44. if (!isCancel(reason)) {
    45. // 再次判断是否要取消,如果是会抛出异常
    46. throwIfCancellationRequested(config);
    47. // 处理正常错误的返回值
    48. if (reason && reason.response) {
    49. reason.response.data = transformData.call(
    50. config,
    51. config.transformResponse,
    52. reason.response
    53. );
    54. reason.response.headers = AxiosHeaders.from(reason.response.headers);
    55. }
    56. }
    57. return Promise.reject(reason);
    58. });
    59. }

    3.5 adapter 请求适配器,此处以xhr请求适配器为例

    dispatchRequest的流程还是相对简单的,剩下的疑惑就是adapter干了些什么,让我们接着往下看吧!

    1. // 用于给上传和下载进度增加监听函数
    2. function progressEventReducer (listener, isDownloadStream) {
    3. let bytesNotified = 0;
    4. const _speedometer = speedometer(50, 250);
    5. return e => {
    6. const loaded = e.loaded;
    7. const total = e.lengthComputable ? e.total : undefined;
    8. const progressBytes = loaded - bytesNotified;
    9. const rate = _speedometer(progressBytes);
    10. const inRange = loaded <= total;
    11. bytesNotified = loaded;
    12. const data = {
    13. loaded,
    14. total,
    15. progress: total ? (loaded / total) : undefined,
    16. bytes: progressBytes,
    17. rate: rate ? rate : undefined,
    18. estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
    19. event: e
    20. };
    21. data[isDownloadStream ? 'download' : 'upload'] = true;
    22. listener(data);
    23. };
    24. }
    25. // 判断是否支持XMLHttpRequest
    26. const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
    27. // 适配器的请求参数是config
    28. export default isXHRAdapterSupported && function (config) {
    29. // 返回Promise
    30. return new Promise(function dispatchXhrRequest (resolve, reject) {
    31. // 请求体
    32. let requestData = config.data;
    33. // 请求头
    34. const requestHeaders = AxiosHeaders.from(config.headers).normalize();
    35. // 返回数据类型
    36. const responseType = config.responseType;
    37. let onCanceled;
    38. //
    39. function done () {
    40. if (config.cancelToken) {
    41. config.cancelToken.unsubscribe(onCanceled);
    42. }
    43. if (config.signal) {
    44. config.signal.removeEventListener('abort', onCanceled);
    45. }
    46. }
    47. // 自动帮我们设置contentType,
    48. // 这就是为什么我们使用的时候都不需要
    49. // 特别设置contentType的原因了
    50. if (utils.isFormData(requestData)) {
    51. if (platform.isStandardBrowserEnv || platform.isStandardBrowserWebWorkerEnv) {
    52. // 浏览器环境让浏览器设置
    53. requestHeaders.setContentType(false);
    54. } else {
    55. requestHeaders.setContentType('multipart/form-data;', false);
    56. }
    57. }
    58. // 请求
    59. let request = new XMLHttpRequest();
    60. // 设置auth,帮我们转码好了
    61. if (config.auth) {
    62. const username = config.auth.username || '';
    63. const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
    64. requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
    65. }
    66. // 拼接完整URL路径
    67. const fullPath = buildFullPath(config.baseURL, config.url);
    68. // 开启请求
    69. request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
    70. // 设置超时时间
    71. request.timeout = config.timeout;
    72. //
    73. function onloadend () {
    74. if (!request) {
    75. return;
    76. }
    77. // 预准备返回体的内容
    78. const responseHeaders = AxiosHeaders.from(
    79. 'getAllResponseHeaders' in request && request.getAllResponseHeaders()
    80. );
    81. const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
    82. request.responseText : request.response;
    83. const response = {
    84. data: responseData,
    85. status: request.status,
    86. statusText: request.statusText,
    87. headers: responseHeaders,
    88. config,
    89. request
    90. };
    91. // 请求完之后判断请求是成功还是失败
    92. // 执行resolve和reject的操作
    93. settle(function _resolve (value) {
    94. resolve(value);
    95. done();
    96. }, function _reject (err) {
    97. reject(err);
    98. done();
    99. }, response);
    100. // 清除request
    101. request = null;
    102. }
    103. if ('onloadend' in request) {
    104. // 设置onloadend
    105. request.onloadend = onloadend;
    106. } else {
    107. // Listen for ready state to emulate onloadend
    108. request.onreadystatechange = function handleLoad () {
    109. if (!request || request.readyState !== 4) {
    110. return;
    111. }
    112. // The request errored out and we didn't get a response, this will be
    113. // handled by onerror instead
    114. // With one exception: request that using file: protocol, most browsers
    115. // will return status as 0 even though it's a successful request
    116. if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
    117. return;
    118. }
    119. // readystate handler is calling before onerror or ontimeout handlers,
    120. // so we should call onloadend on the next 'tick'
    121. // readystate之后再执行onloadend
    122. setTimeout(onloadend);
    123. };
    124. }
    125. // 处理浏览器请求取消事件
    126. request.onabort = function handleAbort () {
    127. if (!request) {
    128. return;
    129. }
    130. reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
    131. request = null;
    132. };
    133. // 处理低级的网络错误
    134. request.onerror = function handleError () {
    135. reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
    136. request = null;
    137. };
    138. // 处理超时
    139. request.ontimeout = function handleTimeout () {
    140. let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
    141. const transitional = config.transitional || transitionalDefaults;
    142. if (config.timeoutErrorMessage) {
    143. timeoutErrorMessage = config.timeoutErrorMessage;
    144. }
    145. reject(new AxiosError(
    146. timeoutErrorMessage,
    147. transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
    148. config,
    149. request));
    150. request = null;
    151. };
    152. // 添加 xsrf
    153. if (platform.isStandardBrowserEnv) {
    154. const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
    155. && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
    156. if (xsrfValue) {
    157. requestHeaders.set(config.xsrfHeaderName, xsrfValue);
    158. }
    159. }
    160. // 无请求体的话就移除contentType
    161. requestData === undefined && requestHeaders.setContentType(null);
    162. // 添加headers
    163. if ('setRequestHeader' in request) {
    164. utils.forEach(requestHeaders.toJSON(), function setRequestHeader (val, key) {
    165. request.setRequestHeader(key, val);
    166. });
    167. }
    168. // 添加withCredentials
    169. if (!utils.isUndefined(config.withCredentials)) {
    170. request.withCredentials = !!config.withCredentials;
    171. }
    172. // 添加responseType
    173. if (responseType && responseType !== 'json') {
    174. request.responseType = config.responseType;
    175. }
    176. // 增加下载过程的监听函数
    177. if (typeof config.onDownloadProgress === 'function') {
    178. request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
    179. }
    180. // 增加上传过程的监听函数
    181. if (typeof config.onUploadProgress === 'function' && request.upload) {
    182. request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
    183. }
    184. // 请求过程中取消
    185. if (config.cancelToken || config.signal) {
    186. onCanceled = cancel => {
    187. if (!request) {
    188. return;
    189. }
    190. reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
    191. request.abort();
    192. request = null;
    193. };
    194. config.cancelToken && config.cancelToken.subscribe(onCanceled);
    195. if (config.signal) {
    196. config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
    197. }
    198. }
    199. // 获取请求协议,比如https这样的
    200. const protocol = parseProtocol(fullPath);
    201. // 判断当前环境是否支持该协议
    202. if (protocol && platform.protocols.indexOf(protocol) === -1) {
    203. reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
    204. return;
    205. }
    206. // 发送请求
    207. request.send(requestData || null);
    208. });
    209. }

  • 相关阅读:
    sql注入(6), 更新注入, 堆叠注入, 宽字节注入
    错误日志:Got permission denied while trying to connect to the Docker daemon socket
    基于python的pdf版本的PPT转换为office PPT
    了解 云原生 和 边缘计算
    elasticsearch bulk数据--ES批量导入json数据
    每天一个知识点-如何保证缓存一致性
    机器学习 笔记06:最大熵模型
    【图像分类损失】PolyLoss:一个优于 Cross-entropy loss和Focal loss的分类损失
    【Web前端基础进阶学习】HTML详解(下篇)
    金仓数据库KingbaseES常用连接池配置指南
  • 原文地址:https://blog.csdn.net/weixin_66128399/article/details/136255864