别催了~ 正在从服务器偷取页面 . . .

axios源码学习


axios简介

axios是什么?

axios是一个基于Promise的HTTP库,可以用在浏览器和node.js之中使用。

axios的特性

  • 从浏览器中创建 XMLHttpRequests

  • 从 node.js 创建 http 请求

  • 支持 Promise API

  • 拦截请求和响应

  • 转换请求数据和响应数据

  • 取消请求

  • 自动转换 JSON 数据

  • 客户端支持防御 XSRF

    axios常用的方法

  • axios(config):最简单的用法,可以发送任何类型的请求

  • axios.request(config):用法跟axios(config)相同

  • axios.get(url[, config]):发送get请求

  • axios.post(url[, data[, config]]):发送post请求

  • axios.delete(url[, config]):发送delete请求

  • axios.put(url[, data[, config]]):发送put请求

  • axios.defaults.xxx: 请求的默认全局配置

  • axios.interceptors.request.use(): 添加请求拦截器

  • axios.interceptors.response.use(): 添加响应拦截器

  • axios.create([config]): 创建一个新的 axios(它没有下面的功能)

  • axios.Cancel(): 用于创建取消请求的错误对象

  • axios.CancelToken(): 用于创建取消请求的 token 对象

  • axios.isCancel(): 是否是一个取消请求的错误

  • axios.all(promises): 用于批量执行多个异步请求

  • axios.spread(): 用来指定接收所有成功数据的回调函数的方法

这里要注意一下请求方式的配置,get、delete和post、put的配置是不一样的

axios主要源码

主要文件目录

├── /dist/ # 项目输出目录 
├── /lib/ # 项目源码目录 
│ ├── /adapters/ # 定义请求的适配器 xhr、http 
│ │ ├── http.js # 实现 http 适配器(包装 http 包) 
│ │ └── xhr.js # 实现 xhr 适配器(包装 xhr 对象) 
│ ├── /cancel/ # 定义取消功能 
│ ├── /core/ # 一些核心功能 
│ │ ├── Axios.js # axios 的核心主类 
│ │ ├── dispatchRequest.js # 用来调用 http 请求适配器方法发送请求的函数 
│ │ ├── InterceptorManager.js # 拦截器的管理器 
│ │ └── settle.js # 根据 http 响应状态,改变 Promise 的状态 
│ ├── /helpers/ # 一些辅助方法 
│ ├── axios.js # 对外暴露接口 
│ ├── /defaults/ # axios 的默认配置 
│ └── utils.js # 公用工具 
├── package.json # 项目信息 
├── index.d.ts # 配置 TypeScript 的声明文件 
└── index.js # 入口文件

入口

根目录下的index.js是入口文件

import axios from './lib/axios.js';
export default axios;

再来到 ./lib/axios.js
从代码可以看出axios其实是Axios的一个实例

import Axios from './core/Axios.js';
  ...
  
/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 *
 * @returns {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  //使用默认配置创建Axios实例
  const context = new Axios(defaultConfig);
  /*
  bind函数的作用跟Axios.prototype.request.bind类似,将
  Axios.prototype.request函数的this显式绑定为context
  返回值instance是一个函数,作用跟Axios.prototype.request一样
  */
  const instance = bind(Axios.prototype.request, context);

  /*
  extend的作用是将Axios.prototype的方法和属性扩展到instance上
  */
  utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});

  //将context的方法和属性扩展到instance上
  utils.extend(instance, context, {allOwnKeys: true});
  
  /*
    工厂函数,用来返回创建实例对象的函数
     createInstance的参数是默认参数+instance的参数
  */
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

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


//下面是直接axios上直接挂载方法
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Expose Cancel & CancelToken
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
axios.toFormData = toFormData;
// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};

...

这块代码比较重要的一点就是instance是一个函数,当它调用时,即相当于axios({})这样调用,实际上就是Axios.prototype.request在执行
/lib/core/Axios.js

// Axios类,初始化了默认配置,请求和响应两个拦截器
class Axios {
  constructor(instanceConfig) {
    this.defaults = instanceConfig;
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }

  /**
   * Dispatch a request
   *
   * @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
   * @param {?Object} config
   *
   * @returns {Promise} The Promise to be fulfilled
   */
  request(configOrUrl, config) {
    /*eslint no-param-reassign:0*/
   /*
   如果configOrUrl是字符串,那么它就是url,config就是配置对象 axios(url,[config])
   否则configOrUrl就是一个配置对象 axios([config])
   */
    if (typeof configOrUrl === 'string') {
      config = config || {};
      config.url = configOrUrl;
    } else {
      config = configOrUrl || {};
    }
     //将默认配置与用户调用时传入的配置进行合并
    config = mergeConfig(this.defaults, config);

    const transitional = config.transitional;
   
    if (transitional !== undefined) {
      validator.assertOptions(transitional, {
        silentJSONParsing: validators.transitional(validators.boolean),
        forcedJSONParsing: validators.transitional(validators.boolean),
        clarifyTimeoutError: validators.transitional(validators.boolean)
      }, false);
    }

    //设置请求方法,如果config中没有配置方法,就采用默认方法或者设置为get
    config.method = (config.method || this.defaults.method || 'get').toLowerCase();

    // Flatten headers
    const defaultHeaders = config.headers && utils.merge(
      config.headers.common,
      config.headers[config.method]
    );

    defaultHeaders && utils.forEach(
      ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
      function cleanHeaderConfig(method) {
        delete config.headers[method];
      }
    );
    //设置请求头
    config.headers = new AxiosHeaders(config.headers, defaultHeaders);

    // 过滤跳过的拦截器
    const requestInterceptorChain = [];
    //是否同步执行
    let synchronousRequestInterceptors = true;
    //此方法对请求拦截器每一项执行函数unshiftRequestInterceptors,把拦截器的每一项存入requestInterceptorChain
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
        return;
      }
     //如果有一个是异步的,那么整个队列都是异步的
      synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
      //unshift,后定义的拦截器是先执行的
      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });
    //响应拦截器,push是先定义先执行
    const responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });

    let promise;
    let i = 0;
    let len;
    //如果是异步的
    if (!synchronousRequestInterceptors) {
      //dispatchRequest是发送请求的函数
      const chain = [dispatchRequest.bind(this), undefined];
      //请求拦截器异步执行完后再执行派发请求,最后再执行响应拦截器
      chain.unshift.apply(chain, requestInterceptorChain);
      chain.push.apply(chain, responseInterceptorChain);
      len = chain.length;
      
      //promise是成功的状态
      promise = Promise.resolve(config);

      while (i < len) {
        //promise是成功状态,所以会执行第一个chain[i++]回调
        promise = promise.then(chain[i++], chain[i++]);
      }
      //返回promise对象
      return promise;
    }
    
    //同步则顺序执行
    len = requestInterceptorChain.length;

    let newConfig = config;

    i = 0;

    while (i < len) {
      const onFulfilled = requestInterceptorChain[i++];
      const onRejected = requestInterceptorChain[i++];
      try {
        newConfig = onFulfilled(newConfig);
      } catch (error) {
        onRejected.call(this, error);
        break;
      }
    }
    
    //请求拦截器执行后再执行派发请求
    try {
      promise = dispatchRequest.call(this, newConfig);
    } catch (error) {
      return Promise.reject(error);
    }
    
    i = 0;
    len = responseInterceptorChain.length;
    //执行响应拦截器,异步执行
    while (i < len) {
      promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
    }
    //返回promise对象
    return promise;
  }

  getUri(config) {
    config = mergeConfig(this.defaults, config);
    const fullPath = buildFullPath(config.baseURL, config.url);
    return buildURL(fullPath, config.params, config.paramsSerializer);
  }
}

  
  
//下面两个forEach是通过Axios的原型挂载各种请求方法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method,
      url,
      data: (config || {}).data
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/

  function generateHTTPMethod(isForm) {
    return function httpMethod(url, data, config) {
      return this.request(mergeConfig(config || {}, {
        method,
        headers: isForm ? {
          'Content-Type': 'multipart/form-data'
        } : {},
        url,
        data
      }));
    };
  }

  Axios.prototype[method] = generateHTTPMethod();

  Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});

export default Axios;

从以上源码可知,Axios工作流程如下

  1. 通过createInstence创建Axios实例axios
  2. 对axios做一些配置
  3. 执行请求拦截器(requestInterceptorManager)
  4. 派发请求(dispatchRequest)
  5. 执行响应拦截器(responseInterceptorManager)
  6. 最后返回一个promise

拦截器

前面在看Axios类的时候contructor里就有两个拦截器

import InterceptorManager from './InterceptorManager.js';

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

到/lib/core/InterceptorManager.js ,可以看到InterceptorManager的具体内容

import utils from './../utils.js';

class InterceptorManager {
  constructor() {
    this.handlers = [];
  }

  /**
   * Add a new interceptor to the stack
   *
   * @param {Function} fulfilled The function to handle `then` for a `Promise`
   * @param {Function} rejected The function to handle `reject` for a `Promise`
   *
   * @return {Number} An ID used to remove interceptor later
   */
  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 = [];
    }
  }

  //这个的作用就是在把拦截器的fullfilled和rejected回调,加入chain(见Axios.js)
  forEach(fn) {
     //遍历this.handlers的每一项,让其去执行第二个函数
    utils.forEach(this.handlers, function forEachHandler(h) {
      if (h !== null) {
        fn(h);
      }
    });
  }
}

export default InterceptorManager;

可以看出,一个InterceptorManager对象都保存着一个handlers数组,这个数组用来保存一个对象,这个对象里包含了许多拦截函数。

  • use方法可以添加拦截器,eject方法可以注销拦截器
  • 请求拦截器是先来后执行,响应拦截器是先来先执行
  • 请求拦截器可以同步或者异步执行,响应拦截器异步执行

/*
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 *
 * @param {Boolean} [allOwnKeys = false]
 * @returns {void}
 */
function forEach(obj, fn, {allOwnKeys = false} = {}) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  let i;
  let l;

  // Force an array if not already something iterable
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }
  //其实就是遍历obj的每一项,让每一项去执行函数fn
  if (isArray(obj)) {
    // Iterate over array values
    for (i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
    const len = keys.length;
    let key;

    for (i = 0; i < len; i++) {
      key = keys[i];
      fn.call(null, obj[key], key, obj);
    }
  }
}

派发请求

dispatchRequest

/**
 * Dispatch a request to the server using the configured adapter.
 *
 * @param {object} config The config that is to be used for the request
 *
 * @returns {Promise} The Promise to be fulfilled
 */
export default function dispatchRequest(config) 
  //如果被取消的请求被发送出去, 抛出错误
  throwIfCancellationRequested(config);
  //确保headers存在
  config.headers = AxiosHeaders.from(config.headers);

  //转换请求数据
  config.data = transformData.call(
    config,
    config.transformRequest
  );

  //获取适配器,可以自定义适配器
  const adapter = config.adapter || defaults.adapter;
  //发送请求, 返回请求后 promise 对象  ajax HTTP
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

    //转换响应数据
    response.data = transformData.call(
      config,
      config.transformResponse,
      response
    );
   
    response.headers = AxiosHeaders.from(response.headers);
    //设置 promise 成功的值为 响应结果
    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData.call(
          config,
          config.transformResponse,
          reason.response
        );
        reason.response.headers = AxiosHeaders.from(reason.response.headers);
      }
    }

    return Promise.reject(reason);
  });
}

可以看到派发请求主要就是以下几步:

  • 设置请求头、适配器、转换请求数据
  • 发送请求
  • 转换响应数据、设置响应头

transformData

在派发请求时,转换数据有以下代码:

//转换请求数据
  config.data = transformData.call(
    config,
    config.transformRequest
  );
//转换响应数据
    response.data = transformData.call(
      config,
      config.transformResponse,
      response
    );
  • 可以看到转换数据时,是将config作为this去调用transformData函数,config.transformRequest和 config.transformResponse,response这些作为参数传给函数

接着可以继续看transformData的实现

/**
 * Transform the data for a request or a response
 *
 * @param {Array|Function} fns A single function or Array of functions
 * @param {?Object} response The response object
 *
 * @returns {*} The resulting transformed data
 */
export default function transformData(fns, response) {
  const config = this || defaults;
  const context = response || config;
  const headers = AxiosHeaders.from(context.headers);
  let data = context.data;
 //fns是处理数据的数组,遍历fns,用data、headers、response去作为参数去调用fn获取data
  utils.forEach(fns, function transform(fn) {
    data = fn.call(config, data, headers.normalize(), response ? response.status : undefined);
  });

  headers.normalize();

  return data;
}
  • transformData本身并没有转换数据,它只是遍历处理的函数去处理
  • 补充一点,就是fns即 transformRequest、transformResponse是处理数据的函数,可以用户自己补充处理函数的

transformRequest

 transformRequest: [function transformRequest(data, headers) {
    const contentType = headers.getContentType() || '';
    //用户是否设置数据为json格式
    const hasJSONContentType = contentType.indexOf('application/json') > -1;
    //是否是对象类型
    const isObjectPayload = utils.isObject(data);
    
    if (isObjectPayload && utils.isHTMLForm(data)) {
      data = new FormData(data);
    }

    const isFormData = utils.isFormData(data);

    if (isFormData) {
      if (!hasJSONContentType) {
        //如果是FormData数据而且没有设置json格式,那么直接返回FormData格式的数据
        return data;
      }
      //有设置就要转换为json格式
      return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
    }

    if (utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      headers.setContentType('application/x-www-form-urlencoded;charset=utf-8', false);
      return data.toString();
    }
    //文件类型数据
    let isFileList;

    if (isObjectPayload) {
      if (contentType.indexOf('application/x-www-form-urlencoded') > -1) {
        return toURLEncodedForm(data, this.formSerializer).toString();
      }

      if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
        const _FormData = this.env && this.env.FormData;

        return toFormData(
          isFileList ? {'files[]': data} : data,
          _FormData && new _FormData(),
          this.formSerializer
        );
      }
    }
    //如果数据是对象类型而且用户规定了json格式,则将数据json化,而且设置Content-Type
    if (isObjectPayload || hasJSONContentType ) {
      headers.setContentType('application/json', false);
      return stringifySafely(data);
    }

    return data;
  }],
  • transformRequest其实就是一个数组里面包含一个对数据进行转换的函数
  • transformResponse也类似

adapter

适配器是就是真正发送请求的模块了,根据node和浏览器环境区分有两种,node环境用的是http.js,浏览器环境用的是xhr.js

export default function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    //请求头、请求数据、请求内容
    let requestData = config.data;
    const requestHeaders = AxiosHeaders.from(config.headers).normalize();
    const responseType = config.responseType;
    let onCanceled;
    ...
    //创建一个XMLHttpRequest对象
    let request = new XMLHttpRequest();

    // HTTP basic authentication
    if (config.auth) {
      const username = config.auth.username || '';
      const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
      requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
    }
    //baseURL+url
    const fullPath = buildFullPath(config.baseURL, config.url);
    //发送请求
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    //设置超时时间
    request.timeout = config.timeout;
    //请求结束处理函数
    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;
    }

    //onloadend是一个
    if ('onloadend' in request) {
      // Use onloadend if available
      request.onloadend = onloadend;
    } else {
      // Listen for ready state to emulate onloadend
      request.onreadystatechange = function handleLoad() {
        if (!request || request.readyState !== 4) {
          return;
        }      
        if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
          return;
        }
        // readystate handler is calling before onerror or ontimeout handlers,
        // so we should call onloadend on the next 'tick'
        //setTimeout是宏任务
        setTimeout(onloadend);
      };
    }

    // Handle browser request cancellation (as opposed to a manual cancellation)
    request.onabort = function handleAbort() {
   
    };

    // Handle low level network errors
    request.onerror = function handleError() {
   
    };

    // Handle timeout
    request.ontimeout = function handleTimeout() {
     
    };

    // Add xsrf header
    // This is only done if running in a standard browser environment.
    // Specifically not if we're in a web worker, or react-native.
    //添加xsrf头部,预防xsrf攻击
    if (utils.isStandardBrowserEnv()) {
      // Add xsrf header   如果请求携带cookie或者同源网站就需要添加
      const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
        && config.xsrfCookieName && cookies.read(config.xsrfCookieName);

      if (xsrfValue) {
        requestHeaders.set(config.xsrfHeaderName, xsrfValue);
      }
    }

    // false, 0 (zero number), and '' (empty string) are valid JSON values
    if (!requestData && requestData !== false && requestData !== 0 && requestData !== '') {
      requestData = null;
    }

    // Send the request
    request.send(requestData);
  });
}

其实就是ajax请求promise化

取消请求

用法:

 axios({
    method: 'GET',
    url: 'http://localhost:3000/posts',
    cancelToken:new CancelToken(function(c){
                cancel = c;
            })
   }).then(response => {
   })

cancel();

在派发请求前就有取消请求

/**
 * 这个函数是将要取消的请求取消,会抛出cancel
 */
function throwIfCancellationRequested(config) {
  //如果需要取消请求,
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

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

export default function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  config.headers = AxiosHeaders.from(config.headers);
  ...
}
class CancelToken {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError('executor must be a function.');
    }
    
    let resolvePromise;
    //往实例对象身上添加promise属性,
    this.promise = new Promise(function promiseExecutor(resolve) {
      /*
      将修改promise对象成功状态的函数暴露出去,就是说resolve()这样调用时,promise状态是成功状态,
      现在赋值给resolvePromise,resolvePromise()这样调用,this.promise状态就会是成功状态
      */
      resolvePromise = resolve;
    });

    const token = this;

    ...
    
    //executor的参数是一个cancel函数,这个函数一执行,resolvePromise就会执行,就会改变this.promise
    //的状态
    executor(function cancel(message, config, request) {
      if (token.reason) {
        // Cancellation has already been requested
        return;
      }

      token.reason = new CanceledError(message, config, request);
      resolvePromise(token.reason);
    });
  }
  
 //用于检查是否需要取消并抛出
  throwIfRequested() {
    if (this.reason) {
      throw this.reason;
    }
  }

  //订阅取消信号
  subscribe(listener) {
    if (this.reason) {
      listener(this.reason);
      return;
    }

    if (this._listeners) {
      this._listeners.push(listener);
    } else {
      this._listeners = [listener];
    }
  }

  //取消订阅信号
  unsubscribe(listener) {
    if (!this._listeners) {
      return;
    }
    const index = this._listeners.indexOf(listener);
    if (index !== -1) {
      this._listeners.splice(index, 1);
    }
  }

  //产生CancelToken的工厂方法
  static source() {
    let cancel;
    const token = new CancelToken(function executor(c) {
      cancel = c;
    });
    return {
      token,
      cancel
    };
  }
}
  • 在上面的使用示例中,构造cancelToken时,参数为executor,executor的参数为c赋值给cancel。在取消请求

时调用cancel(),cancel一执行resolvePromise就会执行,resolvePromise一执行this.promise就是成功的状态
有两个地方会调用 throwIfCancellationRequested函数,在进入派发请求时,如果配置了cancelToken并且token.reason存在,就会抛出,在发送完请求得到响应后,转换数据前也会调用这个函数进行检测。
到底在什么地方取消请求呢?在xhr.js文件中

//如果配置了cancelToken或者signal  
if (config.cancelToken || config.signal) {
      // Handle cancellation
      // eslint-disable-next-line func-names
      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);
      if (config.signal) {
        config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
      }
    }
  • 配置了cancelToken或signal就会取消请求

文章作者: John Doe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 John Doe !
评论
  目录