侧边栏

axios源码分析

发布于 | 分类于 源码分析

axios是非常流行的前端网络请求库,本文将阅读其源码,了解内部实现原理,以及axios中核心的拦截器、cancelToken`等实现

基本使用

了解一款常见的网络框架原理axios,通过适配器模式,在浏览器封装xhr,在node封装http模块,上层的调用保持一致。

需要掌握

  • 请求拦截器和响应拦截器的实现
  • 基于axios进行二次封装,实现业务特定需求
  • 并发限制
  • 用户鉴权认证、JWT

常见问题

  • 如何取消请求:构造cancelToken然后传入请求配置参数,然后可以在发送请求之前手动调用该CancelToken对象的calcel方法来抛出异常,取消本次请求,Promise本身是无法取消的
  • 拦截器是如何实现的:先构造一个队列,将请求拦截器依次放在队列头部,响应拦截器一次放在队列尾部,中间是dispatchRequest方法,然后将该队列构造为promise链式调用。

拦截器

构造函数

js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

然后来看看InterceptorManager这个拦截器管理类

js
function InterceptorManager() {
  this.handlers = [];
}
// 添加拦截器
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};
// 取消拦截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};
// 遍历拦截器
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

核心方法Axios.prototype.request

js
Axios.prototype.request = function request(config) {
	// ...初始化配置
  
	// 构建任务链
  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;
};

理解拦截器也十分容易了

js

// 原始的接口
function dispatchRequest(config){
    let res = {
        params: config,
        code: 200,
        data: "hello response",
    }
    // 省略实际接口请求逻辑
    return Promise.resolve(res);
}
var chain = [dispatchRequest, undefined];

// 请求配置
var config = {
    headers: {
        'x-token': 'xxxx12token'
    },
    params: {
        id: 1
    }
};
var promise = Promise.resolve(config);

var interceptors = {
    request: [
        {
            fulfilled(config) {
                console.log(config);
                return config;
            },
            rejected() {
                console.log("request reject");
            }
        }
    ],
    response: [
        {
            fulfilled(res) {
                console.log(res)
            },
            rejected() {
                console.log("response reject");
            }
        }
    ]
};

interceptors.request.forEach(function unshiftRequestInterceptors(
     interceptor
 ) {
     chain.unshift(interceptor.fulfilled, interceptor.rejected);
 });

interceptors.response.forEach(function pushResponseInterceptors(
     interceptor
 ) {
     chain.push(interceptor.fulfilled, interceptor.rejected);
 });

while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
}

promise.then(res=>{
    console.log(res)
})

取消请求

需要了解cancelable promises proposal,目前该提案已被取消,Promise是不能被取消的,那么axios是如何实现取消请求的呢?

查看文档示例,可以使用下面两种方式取消

js
var CancelToken = axios.CancelToken;
var source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // 处理错误
  }
});

// 取消请求(message 参数是可选的)
// 使用同一个 cancel token 取消多个请求
source.cancel('Operation canceled by the user.');

也可以使用下面方式

js
var CancelToken = axios.CancelToken;
var cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// 取消请求
cancel();

查看CancelToken源码

js
function 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;
    }
		// 设置reason
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

可见看见,如果调用了传入executorcancel方法,在后续的dispatchRequest中会判断是否存在reason来决定是否取消本次请求。

查看发送请求的源码dispatchRequest

js
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}
function dispatchRequest(config) {
  // 判断是否cancelToken是否已经执行了cancel方法,如果已执行,则抛出异常终止后续的promise
  throwIfCancellationRequested(config);
  // ...网路请求逻辑
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
    // ...
  },function(){
    throwIfCancellationRequested(config);
    // ...
  })
}

你要请我喝一杯奶茶?

版权声明:自由转载-非商用-保持署名和原文链接。

本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。