axios源码阅读之应用篇

axios源码阅读之应用篇,阅读本篇文章之前建议先阅读我的上一篇axios源码阅读之文件目录篇

axios请求数据方式

四种方案

// 方案一 axios(options);
axios({
  url,
  method,
  headers,
});
axios.request({
  url,
  method,
  headers,
});
// 方案二 axios(url, options);
axios(url, {
  method,
  headers,
});
axios.request(url, {
  method,
  headers,
});
// 方案三——对于get、delete等方法:axios.get(url, options);
axios.get(url, {
  headers,
});
// 方案四——对于post、put等方法:axios.post(url, data, options);
axios.post(url, data, {
  headers,
});

源码分析

对于方案一、方案二可以看到有两种不同的写法,直接使用axios或通过axios.request访问。

/**
 * 创建一个axios实例
 * @param {Object} defaultConfig 默认配置
 * @returns {Axios} 一个新的Axios实例
 */
function createInstance(defaultConfig) {
  // 创建一个axios上下文
  const context = new Axios(defaultConfig);
  // 将axios实例的request对象绑定axios上下文
  const instance = bind(Axios.prototype.request, context);

  // 将axios实例拷贝到instance并绑定axios上下文
  utils.extend(instance, Axios.prototype, context);

  // 将axios上下文拷贝到instance
  utils.extend(instance, context);

  return instance;
}

// 创建一个默认axios实例,用于导出
const axios = createInstance(defaults);

屏蔽掉两行utils.extend代码后,可以明显的看出,axios指向的是Axios.prototype.request函数。因此我们使用axios(options)和axios.request(options)是一样的。
看到这我们会有这样一个疑问?既然axios指向的是Axios.prototype.request函数。又怎么使用axios.request来调用在Axios原型上的request函数呢?
在这里我们就要看utils.extend这个函数了,utils.extend函数主要是扩展对象属性。

/**
 * 通过拷贝copy对象的属性来扩展extended对象的属性
 *
 * @param {Object} extended 要扩展的对象
 * @param {Object} copy 要复制属性的对象
 * @param {Object} thisArg 要绑定功能的对象
 * @returns {Object} 扩展后的对象
 */
function extend(extended, copy, thisArg) {
  forEach(copy, function assignValue(val, key) {
    if (thisArg && isFunction(val)) {
      extended[key] = bind(val, thisArg);
    } else {
      extended[key] = val;
    }
  });
  return extended;
}

通过utils.extend方法成功的将Aixos原型上的所有属性和方法都绑定到了instance上,因此axios也可以调用Axios原型上的方法。

对于方案三和方案四。

/**
 * 为支持的请求方法设置别名
 */
utils.forEach(
  ["delete", "get", "head", "options"],
  function forEachMethodNoData(method) {
    Axios.prototype[method] = function (url, config = {}) {
      return this.request(
        utils.merge(config, {
          method: method,
          url: url,
        })
      );
    };
  }
);

utils.forEach(["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,
      })
    );
  };
});

其实质是对request方法的某些请求进行简化封装。

取消请求

使用方式

const source = axios.CancelToken.source();
axios.get('/urlA', {
  cancelToken: source.token
});
axios.get('/urlB').then(() => {
  source.cancel("B请求成功了");
});

源码分析

/**
 * “CancelToken”是一个可用于请求取消操作的对象。
 *
 * @class
 * @param {Function} executor 执行器
 */
function CancelToken(executor) {
  if (!utils.isFunction(executor)) {
    throw new TypeError("executor必须是一个函数");
  }
  let resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });
  const token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // 此时已经要求取消
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

/**
 * 返回一个对象,该对象包含一个新的“CancelToken”和一个函数,该函数在调用时将取消“ CancelToken”。
 */
CancelToken.source = function source() {
  let cancel;
  const token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel,
  };
};
// xhr.js
if (config.cancelToken) {
  // 处理取消
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
      return;
    }
    request.abort();
    reject(cancel);
    // 清理请求
    request = null;
  });
}

取消请求是利用Promise的异步操作完成。
通过souce方法暴露出cancel和cancelToken,用户只需要执行cancel函数将异步状态从pending转换成fulfilled即可。
axios通过cancelToken.promise获取到异步实例。然后在then方法中执行request.abort()来取消请求。

配置全局设置

两种方案

// 方案一 (不推荐)
axios.defaults.baseURL = 'http://...';
axios.defaults.timeout = 3000;

// 方案二
const _axios = axios.create({
  baseURL: 'http://...',
  timeout: 3000
});

方案一:通过修改axios默认配置实现;方案二:合并新的配置和默认配置项,然后重新创建axios实例。

源码分析

// 工厂方法:创建一个新实例
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

将默认配置defaults和instanceConfig进行合并;配置的优先级是用户配置 > 默认配置。

拦截器

两种拦截器

拦截器分请求拦截器和响应拦截器。

axios.interceptors.request.use(
  config => {
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  response => {
    return response;
  },
  error => {
    return Promise.reject(error);
  }
);

源码分析

// Axios.js
/**
 * 创建一个axios实例
 *
 * @class
 * @param {Object} instanceConfig axios默认配置
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager(),
  };
}

// 拦截器核心实现逻辑
const chain = [dispatchRequest, undefined];
const 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());
}

// InterceptorManager.js
/**
 * 拦截器管理对象
 *
 * @class
 */
function InterceptorManager() {
  this.handlers = [];
}

/**
  *将新的拦截器添加到堆栈中
  *
  * @param {Function} fulfilled 完成状态处理Promise的“then“函数
  * @param {Function} rejected 拒绝状态处理Promise的“catch”函数
  *
  * @return {Number} 一个ID,用于以后删除拦截器
  */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
  });
  return this.handlers.length - 1;
}

拦截器管理对象的本质是一个数组队列,而拦截器的本质是Promise的应用。
因此添加拦截器的时候需要两个函数,一个用来处理Promise的fulfilled这个状态,一个用来处理Promise的rejected这个状态。这里处理Promise异常的方式不是用catch,而是利用then的第二个参数。

Promise.prototype.then(onFulfilled, onRejected)
onFulfilled 可选
当 Promise 变成接受状态(fulfilled)时调用的函数。该函数有一个参数,即接受的最终结果(the fulfillment value)。如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数
onRejected 可选
当 Promise 变成拒绝状态(rejected)时调用的函数。该函数有一个参数,即拒绝的原因(rejection reason)。 如果该参数不是函数,则会在内部被替换为一个 “Thrower” 函数 (it throws an error it received as argument)。

因此为了确保拦截器异常可以被catch捕获,我们通常会在拦截器的异常处理函数中返回Promise.reject(error)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值