axios源码分析之请求adapter
axios changeLog
注:axios从 v1.7.0-beta.0 支持了fetch
v1.7.0-beta.0 changgeLog
Featuresadapter: add fetch adapter; (#6371) (a3ff99b)Contributors to this releaseavatar Dmitriy Mozgovoyavatar Jay
v1.7.0-beta.0 之前的版本
以 v0.27.2版本为例 是如何使用 Adapter
当处理请求时会使用 dispatchRequest方法,该方法出现了adapter方法,adapter方法中传入了config(即构造axios请求时传入的请求配置,包含headers,请求参数等),
- 当成功时会返回 response
- 当失败时会返回 Promise.reject(reason)
【注】axios\lib\core\dispatchRequest.js
/// ....codevar defaults = require('../defaults');///...code var adapter = config.adapter || defaults.adapter;return adapter(config).then(function onAdapterResolution(response) {throwIfCancellationRequested(config);// Transform response dataresponse.data = transformData.call(config,response.data,response.headers,config.transformResponse);return response;}, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);// Transform response dataif (reason && reason.response) {reason.response.data = transformData.call(config,reason.response.data,reason.response.headers,config.transformResponse);}}return Promise.reject(reason);});
来看看defaults.adapter 到底是什么黑魔法?
【注】一般我们在构造axios 请求时不会自己传入adapter,所以此处不会去关注 config.adapter
var adapter = config.adapter || defaults.adapter;
从dispatchRequest.js 源码中我们可以看到 defaults 来源于 axios\lib\defaults\index.js 文件, 该文件导出了defaults对象,defaults.adapter 来源于getDefaultAdapter 返回的值,
getDefaultAdapter 方法
- 先判断 XMLHttpRequest 是否存在,存在就使用 xhr 请求
- 判断process 是否为 undefined 判断是否为node 环境,然后就用 http 包发起起请求
function getDefaultAdapter() {var adapter;if (typeof XMLHttpRequest !== 'undefined') {// For browsers use XHR adapteradapter = require('../adapters/xhr');} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {// For node use HTTP adapteradapter = require('../adapters/http');}return adapter;
}
【注】axios\lib\defaults\index.js
'use strict';var utils = require('../utils');
var normalizeHeaderName = require('../helpers/normalizeHeaderName');
var AxiosError = require('../core/AxiosError');
var transitionalDefaults = require('./transitional');
var toFormData = require('../helpers/toFormData');var DEFAULT_CONTENT_TYPE = {'Content-Type': 'application/x-www-form-urlencoded'
};function setContentTypeIfUnset(headers, value) {if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {headers['Content-Type'] = value;}
}function getDefaultAdapter() {var adapter;if (typeof XMLHttpRequest !== 'undefined') {// For browsers use XHR adapteradapter = require('../adapters/xhr');} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {// For node use HTTP adapteradapter = require('../adapters/http');}return adapter;
}function stringifySafely(rawValue, parser, encoder) {if (utils.isString(rawValue)) {try {(parser || JSON.parse)(rawValue);return utils.trim(rawValue);} catch (e) {if (e.name !== 'SyntaxError') {throw e;}}}return (encoder || JSON.stringify)(rawValue);
}var defaults = {transitional: transitionalDefaults,adapter: getDefaultAdapter(),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)) {setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');return data.toString();}var isObjectPayload = utils.isObject(data);var contentType = headers && headers['Content-Type'];var isFileList;if ((isFileList = utils.isFileList(data)) || (isObjectPayload && contentType === 'multipart/form-data')) {var _FormData = this.env && this.env.FormData;return toFormData(isFileList ? {'files[]': data} : data, _FormData && new _FormData());} else if (isObjectPayload || contentType === 'application/json') {setContentTypeIfUnset(headers, 'application/json');return stringifySafely(data);}return data;}],transformResponse: [function transformResponse(data) {var transitional = this.transitional || defaults.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 {return JSON.parse(data);} catch (e) {if (strictJSONParsing) {if (e.name === 'SyntaxError') {throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, this.response);}throw e;}}}return data;}],/*** A timeout in milliseconds to abort a request. If set to 0 (default) a* timeout is not created.*/timeout: 0,xsrfCookieName: 'XSRF-TOKEN',xsrfHeaderName: 'X-XSRF-TOKEN',maxContentLength: -1,maxBodyLength: -1,env: {FormData: require('./env/FormData')},validateStatus: function validateStatus(status) {return status >= 200 && status < 300;},headers: {common: {'Accept': 'application/json, text/plain, */*'}}
};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;
v1.7.0-beta.0 之后版本
主要增加fetch 请求方式
adapters文件夹下增加 axios\lib\adapters\adapters.js 文件
重写了 getAdapter 方法
【注】axios\lib\adapters\adapters.js
import utils from '../utils.js';
import httpAdapter from './http.js';
import xhrAdapter from './xhr.js';
import fetchAdapter from './fetch.js';
import AxiosError from "../core/AxiosError.js";const knownAdapters = {http: httpAdapter,xhr: xhrAdapter,fetch: fetchAdapter
}utils.forEach(knownAdapters, (fn, value) => {if (fn) {try {Object.defineProperty(fn, 'name', {value});} catch (e) {// eslint-disable-next-line no-empty}Object.defineProperty(fn, 'adapterName', {value});}
});const renderReason = (reason) => `- ${reason}`;const isResolvedHandle = (adapter) => utils.isFunction(adapter) || adapter === null || adapter === false;export default {getAdapter: (adapters) => {adapters = utils.isArray(adapters) ? adapters : [adapters];const {length} = adapters;let nameOrAdapter;let adapter;const rejectedReasons = {};for (let i = 0; i < length; i++) {nameOrAdapter = adapters[i];let id;adapter = nameOrAdapter;if (!isResolvedHandle(nameOrAdapter)) {adapter = knownAdapters[(id = String(nameOrAdapter)).toLowerCase()];if (adapter === undefined) {throw new AxiosError(`Unknown adapter '${id}'`);}}if (adapter) {break;}rejectedReasons[id || '#' + i] = adapter;}if (!adapter) {const reasons = Object.entries(rejectedReasons).map(([id, state]) => `adapter ${id} ` +(state === false ? 'is not supported by the environment' : 'is not available in the build'));let s = length ?(reasons.length > 1 ? 'since :\n' + reasons.map(renderReason).join('\n') : ' ' + renderReason(reasons[0])) :'as no adapter specified';throw new AxiosError(`There is no suitable adapter to dispatch the request ` + s,'ERR_NOT_SUPPORT');}return adapter;},adapters: knownAdapters
}