阅读axios源码

axios调用方法

  1. axios.create(config)
  2. axios.method(url, config)

实现

js 复制代码
function createInstance(defaultConfig) {
  const context = new Axios(defaultConfig)
  const instance = Axios.prototype.request.bind(context)
  
  // 实现调用方法二
  // 将Axios.prototype方法都复制给instance,context作为this
  utils.extend(instance, Axios.prototype, context)
  // 将context的所有实例属性也复制给instance
  utils.extend(instance, context, null)
  
  // 实现调用方法一
  instance.create = function create(config) {
    return createInstance(mergeConfig(default, config))
  }
  
  return instance
}
const axios = createInstance(defaultConfig)

defaults中有什么

js 复制代码
const defaults = {
  ...,
  headers: {
    common: {
      'Accept': 'application/json, text/plain, */*',
      'Content-Type': undefined
    }
  }
}

utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch'], (method) => {
  defaults.headers[method] = {};
});
  • 由此可见,defaults会为headers中添加common以及其他请求方法为属性名,值为对象

Axios类

js 复制代码
class Axios {
  constructor(instanceConfig) {
    this.defaults = instanceConfig
    this.interceptors = {
      request: new InterceptorManager()
      response: new InterceptorManager(),
    }
  }
  request(configOrUrl, config) {
    if (typeof configOrUrl === 'string') {
      config = config || {}
      config.url = configOrUrl
    } else {
      config = configOrUrl || {}
    }
    
    config = mergeConfig(this.defaults, config)
    config.method = (config.method || thhis.default.method || 'get').toLowerCase()
    
    const { headers } = config;
    let contextHeaders = headers && utils.merge(
      headers.common,
      headers[config.method]
    );
    // 删除冗余
    headers && utils.forEach(
      ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
      (method) => {
        delete headers[method];
      }
    );
    config.headers = AxiosHeaders.concat(contextHeaders, headers);
    
    const requestInterceptorChain = [];
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      ...
      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });
    const responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });
    
    const chain = [dispatchRequest.bind(this), undefined];
    chain.unshift.apply(chain, requestInterceptorChain);
    chain.push.apply(chain, responseInterceptorChain);
    len = chain.length;

    promise = Promise.resolve(config);

    while (i < len) {
      promise = promise.then(chain[i++], chain[i++]);
    }

    return promise;
  }
}
  • 这地方主要是处理了config、URL以及header,然后将请求拦截器逆序加入数组,响应拦截器顺序加入数组,当一个请求发送出去的时候为 请求拦截器逆序执行 -> 发送请求 -> 响应拦截器顺序执行

拦截器

js 复制代码
class InterceptorManager {
  constructor() {
    this.handlers = []
  }
  use(fulfilled, rejected, options) {
    this.handlers.push({
      fulfilled,
      rejected,
      synchronous: options ? options.synchronous : false,
      runWhen: options ? options.runWhen : null
    })
    return this.handlers.length - 1
  }
  eject(id) {
    if (this.handlers[id]) {
      this.handlers[id] = null;
    }
  }
  clear() {
    if (this.handlers) {
      this.handlers = [];
    }
  }
}
  • 拦截器管理就是一个数组,使用use存放成功和失败对应的回调,会返回一个id也就是当前存放的下标,用于取消存放

发送请求的dispatchRequest

js 复制代码
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

  if (config.signal && config.signal.aborted) {
    throw new CanceledError(null, config);
  }
}

function dispatchRequest(config) {
  throwIfCancellationRequested(config); // 判断是否终止了请求
  const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
  
  return adapter(config).then(response => {
    throwIfCancellationRequested(config); // 即使成功了也需要判断在发送请求是否终止了
    ...
    return response
  }, reason => {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);
    }
    return Promise.reject(reason)
  })
}

真正发送请求的adapter

  • Axios 是一个基于 promise 网络请求库,作用于node和浏览器中。 在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests
js 复制代码
const knownAdapters = {
  http: httpAdapter,
  xhr: xhrAdapter
}

// xhrAdapter
function dispatchXhrRequest(config) {
  return new Promise((resolve, reject) => {
    let request = new XMLHttpRequest();
    const fullPath = buildFullPath(config.baseURL, config.url);

    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
    
    request.timeout = config.timeout;
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }
      reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
      // Clean up request
      request = null;
    };
    request.onerror = function handleError() {
      reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
      // Clean up request
      request = null;
    };
    request.ontimeout = function handleTimeout() {
      let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
      const transitional = config.transitional || transitionalDefaults;
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(new AxiosError(
        timeoutErrorMessage,
        transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
        config,
        request));

      // Clean up request
      request = null;
    };
    
    let onCanceled;
    if (config.cancelToken || config.signal) {
      onCanceled = cancel => {
        if (!request) {
          return;
        }
        reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
        request.abort();
        request = null;
      };

      config.cancelToken && config.cancelToken.subscribe(onCanceled); // 调用cancelToken.cancel的时候会去调用订阅的方法,从而可以取消promise
      if (config.signal) {
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
    }
    function onloadend() {
      if (!request) {
        return;
      }
      // Prepare the response
      const responseHeaders = AxiosHeaders.from(
        'getAllResponseHeaders' in request && request.getAllResponseHeaders()
      );
      const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
        request.responseText : request.response;
      const response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config,
        request
      };

      settle(function _resolve(value) {
        resolve(value);
        done();
      }, function _reject(err) {
        reject(err);
        done();
      }, response);

      // Clean up request
      request = null;
    }
    
    function done() { // 执行一些清除操作
      if (config.cancelToken) {
        config.cancelToken.unsubscribe(onCanceled);
      }

      if (config.signal) {
        config.signal.removeEventListener('abort', onCanceled);
      }
    }
    
    request.onloadend = onloadend;
    request.send(requestData || null);
  })
}
  • 总的来说就是根据环境判断可以用node的http还是浏览器的xhr,其中对config传入的cancelToken调用其订阅方法,订阅可以取消promise的方法,当取消的时候会调用request.abort()从而reject掉promise,即使发送了网络请求,在后续adapter根据promise对应的resolve或reject状态会进行判断是否取消过,取消过则丢出错误。

取消请求的CancelToken

js 复制代码
class CancelToken {
  static source() {
    let cancel
    const token = new CancelToken(c => {
      cancel = c
    })
    return {
      token,
      cancel
    }
  }
  
  constructor(executor) {
    const token = this
    let resolvePromise;
    executor(function cancel(message, config, request) {
      if (token.reason) { // 存在reason说明已经调用过
        return
      }
      token.reason = new CanceledError(message, config, request);
      resolvePromise(token.reason)
    })
    
    token.promise = new Promise((resolve, reject) => {
      resolvePromise = resolve
    })
    this.promise.then(cancel => {
      if (!token._listeners) return;

      let i = token._listeners.length;

      while (i-- > 0) {
        token._listeners[i](cancel); // 执行所有订阅的函数,其中包括在adapter中存储的cancel
      }
      token._listeners = null;
    });
  }
  subscribe(listener) {
    if (this.reason) {
      listener(this.reason);
      return;
    }

    if (this._listeners) {
      this._listeners.push(listener);
    } else {
      this._listeners = [listener];
    }
  }
}
  • 总而言之,取消函数是通过向外传递一个变量cancel,该变量又被赋值为一个函数,该函数被调用就会给当前this(也就是token)存放一个reason标志已经调用过,执行promise的resolve方法,从而执行所有订阅的函数,包含了adapter中存储的cancel(也就是会执行request.abort导致request所在的promise状态变为reject)
相关推荐
新酱爱学习26 分钟前
🚀 Web 图片优化实践:通过 AVIF/WebP 将 12MB 图片降至 4MB
前端·性能优化·图片资源
用户916357440951 小时前
CSS中的"后"发制人
前端·css
小满xmlc1 小时前
react Diff 算法
前端
bug_kada1 小时前
Js 的事件循环(Event Loop)机制以及面试题讲解
前端·javascript
bug_kada1 小时前
深入理解 JavaScript 可选链操作符
前端·javascript
小满xmlc1 小时前
CI/CD 构建部署
前端
_AaronWong1 小时前
视频加载Loading指令:基于Element Plus的优雅封装
前端·electron
KallkaGo1 小时前
threejs复刻原神渲染(三)
前端·webgl·three.js
IT_陈寒3 小时前
Vue3性能优化:掌握这5个Composition API技巧让你的应用快30%
前端·人工智能·后端
excel12 小时前
在 Node.js 中用 C++ 插件模拟 JavaScript 原始值包装对象机制
前端