Axios 源码分析

Axios 源码分析

首先从 axios 的几种请求方式来入手,我们从 axios 库中导入的 axios 对象。找到源码 axios.js 类,可以看到创建的默认 axios 对象。

axios.js

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);//创建Axios实例

  //将context绑定到Axios的request方法上
  //也可以这样实现:var instance = Axios.prototype.request.bind(context);
  //instance指向了request方法,并且上下文是实例context
  //所以我们能直接以axios(url, {config})的方式来发送请求。本质上还是调用的request方法
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  //把Axios.prototype上的方法拓展到instance上,同时上下文是context,也就是this指向context
  //所以我们能以axios.get/post的方式发送请求
  utils.extend(instance, Axios.prototype, context);

  //将context上的属性和方法拓展到instance上
  //所以我们以axios.defaults,axios.interceptors能获取到拦截器和默认属性
  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

module.exports = axios;

从以上源码可以得知,axios 中导出的 axios 对象是通过 createInstance 方法以及默认配置 defaults 来创建的。
createInstance 方法没有仅仅创建 Axios 实例,还做了一系列绑定和拓展的操作,使得获得的 Axios 实例支持 axios(url,{config})和 axios.get/post 这种请求方式。

axios 类和 request 方法
function Axios(instanceConfig) {
this.defaults = instanceConfig; //默认配置
this.interceptors = { //拦截器
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

Axios.prototype.request = function request(config) {
if (typeof config === 'string') {//为了支持 axios(url, {config})这种写法
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}

config = mergeConfig(this.defaults, config);//合并配置

//设置请求方法
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}

//通过以 promise resolve, reject 为一组值的链式数组,来支持拦截器中间件,并将配置参数传给 dispatchRequest 方法
// config 配置--> 请求拦截器 --> dispatchRequest--> 响应拦截器
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);

this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);//请求拦截器插入到数组前部
});

this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected); //响应拦截器插入到数组尾部
});

while (chain.length) {//遍历生成最终的请求 promise(包含配置信息,请求拦截器,dispatchRequest 方法,响应拦截器)
promise = promise.then(chain.shift(), chain.shift());
}

return promise;
};

//为了支持 axios.get(url, config)这种写法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/_eslint func-names:0_/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});

//为了支持 axios.post(url, data, config)这种写法
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/_eslint func-names:0_/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});

通过以上源码,Axios 类有默认配置和拦截器两个属性值,同时类上有 Axios 的核心方法 request。
request 方法只声明了一个参数 config,但是通过判断 config 的类型是否是字符串,巧妙的支持了 axios(url, config)这种写法。
config = mergeConfig(this.defaults, config);合并了默认配置和请求上设置的配置。结合 axios 类中的 create 工厂方法的代码可以知道,配置信息的优先级由高到低分别是请求方法上的> 创建 axios 实例的> axios 默认的
axios 支持 promise 是通过在 request 方法中按照 Promise 中的 then 方法中的参数结构,一个 resolve 和一个 reject 为一组将 dispatchRequest,请求拦截器和响应拦截器塞进数组中的。
axios 内部 Promise 的简要流程

Promise.resolve(config).then(function requestInterceptorFulfill(config) {
return config;
}, function requestInterceptorReject(error) {
return Promise.reject(error);
}).then(function dispatchrequest(config) {
return dispatchRequest(config);
}, undefined).then(function responseInterceptorFulfill(response) {
return response;
}, function responseInterceptorReject(error) {
return Promise.reject(error);
});
dispatchRequest 分析

通过上面的源码,我们知道了 axios 是如何支持拦截器的,以及 config 在内部的流动方向。其中,有个 dispatchRequest 方法,还没有分析它做了什么。

从字面意思来看,dispatchRequest 就是发送请求的意思,查看源码,可以发现这个方法主要做了这几件事情:

1.支持取消请求 2.对请求数据做转换 3.处理请求头 4.使用网络请求适配器 adapter 以及配置 config 发送请求 5.对返回数据做转换

module.exports = function dispatchRequest(config) {

 //如果设置了cancelToken则直接取消请求,后续会分析取消请求的相关源码
  throwIfCancellationRequested(config);

  // 确保headers存在
  config.headers = config.headers || {};

  // 对请求的数据做转换
    config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 合并headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  );

  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

 // 获取config上设置的网络请求适配器(若没有则使用默认的)
 // axios中有两个预定义的适配器:分别是nodejs中的http和浏览器中的XMLHttpRequest

  var adapter = config.adapter || defaults.adapter;

  //将配置config传入adpater中,return这个promise
  return adapter(config).then(function onAdapterResolution(response) {
    //如果设置了cancelToken则直接取消请求
    throwIfCancellationRequested(config);

    // 对返回的数据做转换
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // 对返回的数据做转换
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
};
adapter-xhr 分析

xhr 的精简源码,删除了一些非重点代码

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {

    //构造一个XMLHttpRequest对象
    var request = new XMLHttpRequest();

    //构造请求完整路径(相对路径->绝对路径)
      var fullPath = buildFullPath(config.baseURL, config.url);

     //根据配置config中的数据,初始化请求
     //open方法三个参数分别为:请求方法,url,是否异步
     //https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/open
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    //设置监听请求的onreadystatechange回调事件
    request.onreadystatechange = function handleLoad() {
     //响应头
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      //响应数据
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;

     //构造axios中的响应对象
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };
      //根据响应的状态,返回promise的reslove或reject
      settle(resolve, reject, response);
      request = null;
    };

     //设置监听请求的onabort回调事件
     request.onabort = function handleAbort() {
          reject(createError('Request aborted', config, 'ECONNABORTED', request));

      request = null;
    };
        //设置监听请求的onerror回调事件
        request.onerror = function handleError() {

      reject(createError('Network Error', config, null, request));

      request = null;
    };

   //设置监听请求的ontimeout回调事件
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
        request));
      request = null;
    };

       //若设置了cancelToken,则取消请求
       if (config.cancelToken) {
          config.cancelToken.promise.then(function onCanceled(cancel) {
        request.abort();//中断请求
        reject(cancel);//使用cancel信息返回promise的reject
        request = null;
      });
    }

    if (requestData === undefined) {
      requestData = null;
    }

    request.send(requestData);//使用请求数据requestData,最终发送请求
  });
};

可以看到,adapter 中封装了使用 XMLHttpRequest 的具体细节,包括,创建 XHR 对象,初始化请求,构造请求链接,设置请求参数,构造响应对象等等

取消请求

CancelToken.js

function CancelToken(executor) {

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

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {//已经取消过了
      return;
    }

    //构造Cancel类,用于标志是否是取消请求,同时设置取消请求的信息
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });


    //xhr.js
    if (config.cancelToken) {
      // 处理取消请求的情况
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();//中断请求
        reject(cancel);
        request = null;
      });
    }

通过以上源码,我们知道
1.CancelToken 内部声明了 promise 成员变量 this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; });

2.构造 CancelToken 对象的时候,传入的 executor 方法在其中执行,并传入了一个 cancel 方法作为参数,在这个 cancel 方法中,判断了这个请求是否已经取消过,构造了 Cancel 类,用于存储取消信息,然后将 cancel 对象通过保存的 promise 的 reslove 方法传出去。

3.在 xhr 代码中,第二步 resolve 的 cancel 对象,通过 then 方法继续传递,并在其中中断了请求,并通过 xhr 的 promise 的 reject 方法传到外部。也就是我们使用 axios 请求的 catch 中得到的。

4.在使用 CancelToken 的时候,会把第 2 步中的 cancel 方法保存下来,当需要取消请求的时候再像这样调用。cancel(‘Cancel by user!’)。方法参数就是 Cancel 对象中的 message。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶落风尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值