博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Axios源码分析
阅读量:7029 次
发布时间:2019-06-28

本文共 13932 字,大约阅读时间需要 46 分钟。

目录结构

│├ lib/│ ├ adapters/│ ├ cancel/│ ├ core/│ ├ helpers/│ ├ axios.js│ ├ defaults.js     // 默认配置│ └ utils.js│├ index.js // 入口复制代码

从入口开始

从入口我们可以知道axios提供了些什么

1. 从webpack.config得知入口文件是index.js

// index.jsmodule.exports = require('./lib/axios');复制代码

从这里我们知道库的入口文件是lib/axios.js

2. lib/axios.js导出了什么

// lib/axios.jsmodule.exports = axios;module.exports.default = axios;复制代码

导出了 "axios" 这个对象,并且还有兼容import写法

3. 导出的axios是啥

var defaults = require('./defaults');function createInstance(defaultConfig) {  // ...}var axios = createInstance(defaults);复制代码

这里可以看出axios是一个实例,并且使用了默认参数。

4. 导出的axios提供了啥

a. axios.Axios

// lib/axios.jsaxios.Axios = Axios;复制代码

这里的Axios是该实例的类

b. axios.create

// lib/axios.jsaxios.create = function create(instanceConfig) {  return createInstance(mergeConfig(axios.defaults, instanceConfig));};复制代码

axios还提供了给用户自己创建实例的方法,并且用户传入的config能覆盖默认config(mergeConfig的逻辑)

c. axios.Cancelaxios.CancelTokenaxios.isCancel

// lib/axios.jsaxios.Cancel = require('./cancel/Cancel');axios.CancelToken = require('./cancel/CancelToken');axios.isCancel = require('./cancel/isCancel');复制代码

axios提供了取消请求的方法

d. axios.all

// lib/axios.jsaxios.all = function all(promises) {  return Promise.all(promises);};复制代码

Promise.all的封装

e. axios.spread

// lib/axios.jsaxios.spread = require('./helpers/spread');复制代码

参数解构的封装,类似es6数组的...操作符

Axios类

从入口来看,我们发现了重点方法axios.create,并且知道它是用方法createInstance创建实例的。

// lib/axios.jsvar Axios = require('./core/Axios');function createInstance(defaultConfig) {  var context = new Axios(defaultConfig);  // ...}复制代码

这里用到了Axios类,并且它是整个库的核心,所以,接下来我们看看这个类是怎么定义的。

1. 构造器

// core/Axios.jsfunction Axios(instanceConfig) {  this.defaults = instanceConfig;  this.interceptors = {    request: new InterceptorManager(),    response: new InterceptorManager()  };}复制代码

构造器做了两件事:i.初始化配置、ii.初始化拦截器。

2. 原型链方法

a. Axios.prototype.request

发起一个请求

发送请求,以及对请求的处理,这部分我们放在下一节详细分析。

b. Axios.prototype.getUri

获取请求完整地址

c. Axios.prototype.get,Axios.prototype.post...

请求的别名

  • 语法糖,使得请求调用更加的语义化,更加的方便,其实现是基于Axios.prototype.request
  • 对于delete, get, head, options
function forEachMethodNoData(method) {  Axios.prototype[method] = function(url, config) {    return this.request(utils.merge(config || {}, {      method: method,      url: url    }));  };}复制代码
  • 对于post, put, patch
function forEachMethodWithData(method) {  Axios.prototype[method] = function(url, data, config) {    return this.request(utils.merge(config || {}, {      method: method,      url: url,      data: data    }));  };}复制代码

3. Axios.prototype.request

这里是axios的核心实现,包含了配置合并,请求发送,拦截器加入

// core/Axios.jsAxios.prototype.request = function request(config) {    if (typeof config === 'string') {        config = arguments[1] || {};        config.url = arguments[0];    } else {        config = config || {};    }    // ...}复制代码

对参数约定进行判断,使得调用的时候可以更灵活。 比如:axios.request('api.example.com', config)axios.request(config)

// core/Axios.jsconfig = mergeConfig(this.defaults, config);config.method = config.method ? config.method.toLowerCase() : 'get';复制代码

合并配置,并且对方法有小写处理,所以config传入不用在意大小写。

重点来了,这里不仅处理了请求,还用了一个很巧妙的方法去处理请求和拦截器,以及拦截器之间的先后顺序。

// Hook up interceptors middleware  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 = promise.then(chain.shift(), chain.shift());  }  return promise;复制代码

首先执行到循环前我们看看chain是啥结构

[请求拦截1成功,请求拦截1失败,...,dispatchRequest, undefined,响应拦截1成功,响应拦截1失败,...]复制代码

入口通过Promise.resolve(config)将配置传入,在经历请求拦截器处理后,发起请求,请求成功获得相应,再依次经历响应拦截器处理。

dispatchRequest

发起请求的主要方法

处理URL
// core/dispatchRequest.jsif (config.baseURL && !isAbsoluteURL(config.url)) {    config.url = combineURLs(config.baseURL, config.url);}复制代码

区分相对路径和绝对路径

处理请求的data
// core/dispatchRequest.js// Transform request dataconfig.data = transformData(    config.data,    config.headers,    config.transformRequest);复制代码

处理PUT, POST, PATCH传入的data

处理Headers
// core/dispatchRequest.js// Flatten headersconfig.headers = utils.merge(    config.headers.common || {},    config.headers[config.method] || {},    config.headers || {});复制代码

这里的config已经是合并以及处理过的,这里分了三个headers

  1. 默认的通用headers
  2. 请求方法对应的headers
  3. 用户传入的headers

优先级依次递增,将这三个headers合并平铺到config.headers

紧接着删除config.headers下非http header的属性

// core/dispatchRequest.jsutils.forEach(    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],    function cleanHeaderConfig(method) {      delete config.headers[method];    });复制代码
★请求适配器★

在不同环境下,发起请求的方式可能会不一样,比如:在Node环境下发起请求可能是基于http模块,而在浏览器环境下发起请求又可能是基于XMLHttpRequest对象

但是适配器的存在,创建了一个通用的接口,它有通用的输入输出。即不用管内部实现的差异,只需要按照约定输入,以及处理约定的输出即可。

默认适配器:

// core/dispatchRequest.jsvar defaults = require('../defaults');var adapter = config.adapter || defaults.adapter;复制代码
// defaults.jsfunction getDefaultAdapter() {  var adapter;  // Only Node.JS has a process variable that is of [[Class]] process  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {    // For node use HTTP adapter    adapter = require('./adapters/http');  } else if (typeof XMLHttpRequest !== 'undefined') {    // For browsers use XHR adapter    adapter = require('./adapters/xhr');  }  return adapter;}var defaults = {    adapter: getDefaultAdapter()    // ...}复制代码

axios内置了两个适配器adapters/http,adapters/xhr,在初始化默认配置时,它判断了当前环境,并且应用了相应的适配器。

当然你也可以传入自己的适配器,并且会被优先使用。适配器可以应用于小程序开发等有自己的请求方法的场景。之后我们再来看看如何新建一个自定义适配器。

接下来是处理请求适配器的返回

// core/dispatchRequest.jsreturn adapter(config).then(function onAdapterResolution(response) {    throwIfCancellationRequested(config);    // Transform response data    response.data = transformData(      response.data,      response.headers,      config.transformResponse    );    return response;}, function onAdapterRejection(reason) {    if (!isCancel(reason)) {      throwIfCancellationRequested(config);      // Transform response data      if (reason && reason.response) {        reason.response.data = transformData(          reason.response.data,          reason.response.headers,          config.transformResponse        );      }    }    return Promise.reject(reason);});复制代码

这里就是简单的处理了成功和失败情况,格式化数据。

这里的transformData做的事就是将用户传入的config.transformResponse全部执行一遍

throwIfCancellationRequested后面再细讲。

4. InterceptorManager

拦截管理器,拦截器的增删,遍历

构造器

function InterceptorManager() {  this.handlers = [];}复制代码

this.handlers存的就是所有拦截器

InterceptorManager.prototype.use

// core/InterceptorManager.jsInterceptorManager.prototype.use = function use(fulfilled, rejected) {  this.handlers.push({    fulfilled: fulfilled,    rejected: rejected  });  return this.handlers.length - 1;};复制代码

新增一个拦截器并且返回拦截器序号(id)

InterceptorManager.prototype.eject

// core/InterceptorManager.jsInterceptorManager.prototype.eject = function eject(id) {  if (this.handlers[id]) {    this.handlers[id] = null;  }};复制代码

根据序号(id)删除一个拦截器

InterceptorManager.prototype.forEach

// core/InterceptorManager.jsInterceptorManager.prototype.forEach = function forEach(fn) {  utils.forEach(this.handlers, function forEachHandler(h) {    if (h !== null) {      fn(h);    }  });};复制代码

遍历拦截器

使用

  1. Axios的构造函数中,初始化了两个拦截管理器
// core/Axios.jsfunction Axios(instanceConfig) {  this.defaults = instanceConfig;  this.interceptors = {    request: new InterceptorManager(),    response: new InterceptorManager()  };}复制代码

对于每一个Axios实例,都可以访问自己的两个拦截管理器实例,对其进行增删

  1. Axios.prototype.request方法中,将拦截器全部丢到队列里执行。

请求取消

首先我们举个例子来看看如何取消请求

const CancelToken = axios.CancelToken;let cancel;axios.get('/user/12345', {  cancelToken: new CancelToken(function executor(c) {    // An executor function receives a cancel function as a parameter    cancel = c;  })});// cancel the requestcancel();复制代码

lib/axios.js

// lib/axios.jsaxios.CancelToken = require('./cancel/CancelToken');复制代码

接下来我们看看CancelToken类做了什么

CancelToken类

1. 构造器

// cancel/CancelToken.jsfunction CancelToken(executor) {  if (typeof executor !== 'function') {    throw new TypeError('executor must be a function.');  }  var resolvePromise;  this.promise = new Promise(function promiseExecutor(resolve) {    resolvePromise = resolve;  });  var token = this;  executor(function cancel(message) {    if (token.reason) {      // Cancellation has already been requested      return;    }    token.reason = new Cancel(message);    resolvePromise(token.reason);  });}复制代码

构造器中用到了两个属性

cancelToken.promise
cancelToken.reason

构造器接收一个参数executor,并且在构造器最后执行,传入一个cancel function作为参数

即在例子中传入的config

{  cancelToken: new CancelToken(function executor(c) {    // An executor function receives a cancel function as a parameter    cancel = c;}复制代码

这里的c引用的就是那个cancel function

这个cancel function可以随时调用,并且在调用后,会将cancelToken.promisereslove掉,这有什么用吗你可能会问。

// adapters/xhr.jsvar request = new XMLHttpRequest();// ...config.cancelToken.promise.then(function onCanceled(cancel) {    if (!request) {      return;    }    request.abort();    // ...});复制代码

当调用cancel function的时候,会终止掉请求,这就是cancel的实现原理

2. CancelToken.source

// cancel/CancelToken.jsCancelToken.source = function source() {  var cancel;  var token = new CancelToken(function executor(c) {    cancel = c;  });  return {    token: token,    cancel: cancel  };};复制代码

静态方法source,自动新建cancelToken实例,并且将cancel function绑定返回

3. CancelToken.prototype.throwIfRequested

实例方法

// cancel/CancelToken.jsCancelToken.prototype.throwIfRequested = function throwIfRequested() {  if (this.reason) {    throw this.reason;  }};复制代码

当取消时,抛错

Axios.prototype.requestdispatchRequest

// core/dispathRequest.jsfunction throwIfCancellationRequested(config) {  if (config.cancelToken) {    config.cancelToken.throwIfRequested();  }}module.exports = function dispatchRequest(config) {  throwIfCancellationRequested(config);  // ...  return adapter(config).then(function onAdapterResolution(response) {    throwIfCancellationRequested(config);    // ...  }, function onAdapterRejection(reason) {    if (!isCancel(reason)) {      throwIfCancellationRequested(config);      // ...    }  });}复制代码

在请求前后都调用了throwIfCancellationRequested方法,在请求前取消不会发起请求,在请求后取消导致reject

Cancel类

1. 构造器

function Cancel(message) {  this.message = message;}复制代码

2. 重写了toString方法

3. Cancel.prototype.__CANCEL__

默认为true

4. 使用

// cancel/CancelToken.jstoken.reason = new Cancel(message);复制代码

token.reasonCancel的实例,它表示了cancelToken的状态。而验证状态是由接下来这个方法实现的。

isCancel方法

module.exports = function isCancel(value) {  return !!(value && value.__CANCEL__);};复制代码

xhrAdapter

浏览器端的请求适配器

// adapters/xhr.jsmodule.exports = function xhrAdapter(config) {    return new Promise(function dispatchXhrRequest() {})})复制代码

接收配置config作为参数,返回一个promise

dispatchXhrRequest就是ajax的封装,来看看他是怎么封装的

  1. 首先,最基础的一个请求建立,发送流程
// adapters/xhr.jsvar requestData = config.data;var requestHeaders = config.headers;var request = new XMLHttpRequest();// ...request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);// ...request.onreadystatechange = function handleLoad() {}// ...// 设置headersif ('setRequestHeader' in request) {      utils.forEach(requestHeaders, function setRequestHeader(val, key) {        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {          // Remove Content-Type if data is undefined          delete requestHeaders[key];        } else {          // Otherwise add header to the request          request.setRequestHeader(key, val);        }      });}// ...if (requestData === undefined) {      requestData = null;}request.send(requestData);复制代码
  1. 设置超时
// adapters/xhr.jsrequest.timeout = config.timeout;// ...request.ontimeout = function handleTimeout() {      reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',        request));      // Clean up request      request = null;};// ...复制代码
  1. 上传下载进度
// adapters/xhr.jsif (typeof config.onDownloadProgress === 'function') {      request.addEventListener('progress', config.onDownloadProgress);}if (typeof config.onUploadProgress === 'function' && request.upload) {      request.upload.addEventListener('progress', config.onUploadProgress);}// ...复制代码
  1. 取消请求,之前已经提及过
// adapters/xhr.jsif (config.cancelToken) {    // ...}复制代码
  1. 处理错误
request.onerror = function handleError() {      // Real errors are hidden from us by the browser      // onerror should only fire if it's a network error      reject(createError('Network Error', config, null, request));      // Clean up request      request = null;};复制代码
  1. 处理中断
request.onabort = function handleAbort() {      if (!request) {        return;      }      reject(createError('Request aborted', config, 'ECONNABORTED', request));      // Clean up request      request = null;};复制代码

由于在手动触发cancel时有reject,因此这里判断当没有request的时候不重复reject

wxAdapter

在处理非预设环境时,可以自定义适配器

import axios from 'axios'const wxrequest = axios.create({  adapter: function (config) {    return new Promise(function (resolve, reject) {      var response = {        statusText: '',        config: config      }      var request = wx.request({        url: config.url,        data: config.data,        method: config.method.toUpperCase(),        header: config.headers,        responseType: config.responseType,        success(res) {          response.data = res.data          response.status = res.statusCode          response.headers = res.headers          resolve(response)        },        fail(err) {          reject(err)          request = null        }      })      response.request = request      if (config.cancelToken) {        config.cancelToken.promise.then(function onCanceled(cancel) {          if (!request) {            return          }          request.abort()          reject(cancel)          request = null        })      }    })  }})export default wxrequest复制代码

在适配器中加入取消功能,格式化返回数据

总结

拦截器和适配器的设计,使得axios十分的灵活,更易扩展,其实现方式也值得学习使用。

转载于:https://juejin.im/post/5c3b0f9df265da61641436a9

你可能感兴趣的文章
linux下简易端口扫描器
查看>>
HDU 1205
查看>>
Openstack-L 路由注入方式
查看>>
利用ROS工具从bag文件中提取图片
查看>>
Java常用类库
查看>>
Android开发之Activity转场动画
查看>>
List集合三种遍历方法
查看>>
【译】OpenDaylight控制器:YANG Schema和Model
查看>>
C#访问修饰符(public,private,protected,internal,sealed,abstract)
查看>>
android消息线程和消息队列
查看>>
EXCEL中计算不重复单元格的个数
查看>>
二层设备与三层设备的区别--总结
查看>>
安装pytorch成功但cuda不可用
查看>>
unity__DrawCall的理解
查看>>
springboot架构下运用shiro后在configuration,通过@Value获取不到值,总是为null
查看>>
SQLServer 数据库镜像+复制切换方案
查看>>
Postman初探
查看>>
仿淘宝头像上传功能(一)——前端篇。
查看>>
Eclipse通过集成svn实现版本控制
查看>>
OS开发过程中常用开源库
查看>>