前端入门之(axios解析)

前言:vue已经放弃维护vue-resource了,然后推荐使用axios,这一推荐不得了了,axios的人气大增啊,在github已经是50多k的star了,axios算是成功打入了vue全家桶了,既然官方这么推荐,我们接下来就来研究一下axios,顺便再啰嗦几句,我们一直在研究这个框架那个框架的,其实说白了,框架其实就是别人的一种编程思想,都是一些大牛总结的一些编程经验,形象一点就比如一辆汽车的高配与低配,都能够跑,但是感觉就是不一样哈,所以多研究别人好的东西能让自己少走很多弯路,还会让自己变得更强,哈哈~~ 加油,骚年!

我们直接用vue-cli创建一个webpack工程(怎么创建一个vue工程我就不bb了哈),然后我们运行我们的vue工程:

在这里插入图片描述

接着我们去github上copy一份axios的源码放到我们工程里面。

axios官网

我们直接把它lib目录的所有文件copy一份:
https://github.com/axios/axios/tree/master/lib

在这里插入图片描述

可以看到,我们已经把axios的源码copy进工程了,接下来我们先用一下它,我们先简单用node搭建一个属于自己的服务器(怎么搭建服务器我前面有一篇文章有介绍过,感兴趣的小伙伴可以去看一看node搭建简易服务器,设置跨域访问

在这里插入图片描述

好啦,服务器搭建好了,我们简单的用一下axios:

HelloWorld.vue:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
import axios from '../lib/axios'
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Axios'
    }
  },
  created () {
    axios.get('http://localhost:8090/home').then((res) => {
      console.log(res)
    }).catch((erro) => {
      console.log(erro)
    })
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

好啦,我们运行一下项目可能会报这样的错误(如果是直接用vue-cli创建的工程):
在这里插入图片描述

这是因为axios的源码用es5写的,我们工程babel配置文件需要修改一下:
.babelrc

{
  "presets": [
    [
      "env",
      {
        "modules": false,
        "targets": {
          "browsers": [
            "> 1%",
            "last 2 versions",
            "not ie <= 8"
          ]
        }
      }
    ],
    "stage-2"
  ],
  "plugins": [
    "transform-vue-jsx",
    [
      "transform-runtime",
      {
        "helpers": false,
        "polyfill": false
      }
    ]
  ]
}

好啦,我们再次运行我们的项目:
在这里插入图片描述

可以看到,我们已经请求成功了,axios的用法我就不在这里啰嗦了,小伙伴直接去看axios的文档就好了,我们重点研究一下它是怎么工作了。

首先我们找到axios的入口:
axios.js

'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

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

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

// Factory for creating new instances
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

可以看到,入口文件其实没有什么内容,当我们在demo使用get请求的时候,我们走了什么内容呢?

created () {
    axios.get('http://localhost:8090/home').then((res) => {
      console.log(res)
    }).catch((erro) => {
      console.log(erro)
    })
  }

那么axios是一个什么的对象呢?我们看到这么一段代码:

function createInstance(defaultConfig) {
  //创建一个Axios对象
  var context = new Axios(defaultConfig);
  //创建一个axios实例
  var instance = bind(Axios.prototype.request, context);
  
  //copy一份Axios的原型给instance对象
  utils.extend(instance, Axios.prototype, context);
  
  //让instance继承context的所有属性
  utils.extend(instance, context);

  return instance;
}

通过源码我们可以看到,其实axios对象也就是返回的instance其实原型就是Axios,所以我们接下来看一下Axios。
/core/Axios.js

'use strict';

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');

/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);
  config.method = config.method ? config.method.toLowerCase() : 'get';

  // 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;
};

Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

代码有点多,我们直接看重点,我们demo中用了get方法,我们来一下Axios中的get方法定义:

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
Axios.prototype.request = function request(config) {
  ...
  return promise;
};

在官方文档中我们可以看到,除了可以用demo中的:

axios.get('http://localhost:8090/home').then((res) => {
      console.log(res)
    }).catch((erro) => {
      console.log(erro)
    })

这种方式访问网络请求外,我们还可以用:

 created () {
      axios({
        url: 'http://localhost:8090/home',
        method: 'GET'
      }).then((res) => {
        console.log(res)
      }).catch((erro) => {
        console.log(erro)
      })
    }

通过源码我们发现,不管我们用那种方式请求,我们最后都会走Axios的request方法:

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);
  config.method = config.method ? config.method.toLowerCase() : 'get';

  // 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;
};

我们可以看到,request方法直接返回了一个promise对象,所以我们才可以直接通过then拿到返回的结果的。

我们在Axios的request方法中看到这么一段代码:

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());
  }

我们可以看到有一个叫chain的数组,chain英文翻译是链条的意思,我们在这可以形象的把它叫做流水线,一个产品从原材料–>最后的产品会通过一个叫chain的流水线,chain数组中的每一个元素都是一个小组,从对request的封装到最后对response的封装,最后返回需要的result。

//流水线的第一个小组默认是dispatchRequest
  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());
  }

嘿嘿~感觉自己进过厂一样! 原谅我只能以这样的形式来描述了。

在demo中我们可以看到,我们并没有对原材料跟产品做任何处理:

 created () {
    axios.get('http://localhost:8090/home').then((res) => {
      console.log(res)
    }).catch((erro) => {
      console.log(erro)
    })
  }

所以在request中不会走:

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);
  });

对于interceptors我们之后再研究了,既然拦截器的代码不走了,我们就重点关注一下dispatchRequest函数,不管有没有拦截器,dispatchRequest方法都会默认执行的:

code/dispatchRequest.js

'use strict';

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');

/**
 * Throws a `Cancel` if cancellation has been requested.
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

/**
 * 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
 */
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }

  // Ensure headers exist
  config.headers = config.headers || {};

  // Transform request data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  var adapter = config.adapter || defaults.adapter;

  return 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);
  });
};

代码还是有点多,我们看重点:

module.exports = function dispatchRequest(config) {
  .....
  var adapter = config.adapter || defaults.adapter;

  return 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);
  });
};

可以看到,最后直接执行了adapter(config)方法,然后返回了一个promise对象,那么adapter方法又是哪来的呢?

var adapter = config.adapter || defaults.adapter;

可以看到,我们demo中除了传递了一个url外根本就没有传递其它的参数,所以adapter就直接用的是 defaults.adapter,我们去找到它的源码。

defaults.js

'use strict';

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
  }
}

function 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(),

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');
    if (utils.isFormData(data) ||
      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)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],

  /**
   * A timeout in milliseconds to abort a request. If set to 0 (default) a
   * timeout is not created.
   */
  timeout: 0,

  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',

  maxContentLength: -1,

  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  }
};

defaults.headers = {
  common: {
    'Accept': 'application/json, text/plain, */*'
  }
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;

代码还是有点多,我们直接看adapter属性:

var defaults = {
  adapter: getDefaultAdapter()
  ....
function 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;
}

可以看到,在获取adapter的时候还做了环境的判断,如果是node环境的时候adapter=require(’./adapters/http’);如果是浏览器环境的时候adapter = require(’./adapters/xhr’);因为我们走的是浏览器环境,所以我们重点看adapter = require(’./adapters/xhr’);

adapters/xhr.js:

'use strict';

var utils = require('./../utils');
var settle = require('./../core/settle');
var buildURL = require('./../helpers/buildURL');
var parseHeaders = require('./../helpers/parseHeaders');
var isURLSameOrigin = require('./../helpers/isURLSameOrigin');
var createError = require('../core/createError');

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Let the browser set it
    }

    var request = new XMLHttpRequest();

    // HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password || '';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    request.timeout = config.timeout;

    // Listen for ready state
    request.onreadystatechange = function handleLoad() {
      if (!request || request.readyState !== 4) {
        return;
      }

      // The request errored out and we didn't get a response, this will be
      // handled by onerror instead
      // With one exception: request that using file: protocol, most browsers
      // will return status as 0 even though it's a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
        return;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      settle(resolve, reject, response);

      // Clean up request
      request = null;
    };

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

      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      // Clean up request
      request = null;
    };

    // Handle low level network errors
    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;
    };

    // Handle timeout
    request.ontimeout = function handleTimeout() {
      reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
        request));

      // Clean up request
      request = null;
    };

    // 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.
    if (utils.isStandardBrowserEnv()) {
      var cookies = require('./../helpers/cookies');

      // Add xsrf header
      var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
        cookies.read(config.xsrfCookieName) :
        undefined;

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    // Add headers to the request
    if ('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);
        }
      });
    }

    // Add withCredentials to request if needed
    if (config.withCredentials) {
      request.withCredentials = true;
    }

    // Add responseType to request if needed
    if (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

    if (requestData === undefined) {
      requestData = null;
    }

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

哈哈~ 看到这里的时候小伙伴有没有很熟悉了呢? 没错!! 经过跋山涉水、历尽千辛万苦我们终于是看到了axios的真面目了,当然!axios不只是生产浏览器的产品,还生产node的产品,所以它的底层工厂还有一个叫http.js的(有兴趣的童鞋直自己去研究一下哈)。

好啦! 找了大半天,我们继续回到dispatchRequest.js中的dispatchRequest方法:

unction dispatchRequest(config) {
  ...
  //获取XMLHttpRequest对象
  var adapter = config.adapter || defaults.adapter;
  //发送ajax请求,返回promise对象
  return adapter(config).then(function onAdapterResolution(response) {
	//返回请求数据
    return response;
  }, function onAdapterRejection(reason) {
  //请求异常
    return Promise.reject(reason);
  });

通过回掉的方式拿到请求的返回值,最后回掉给客户端:

 axios.get('http://localhost:8090/home').then((res) => {
      console.log(res)
    }).catch((erro) => {
      console.log(erro)
    })

好啦! 整个axios的工作流程我们简单的来回顾一下:

1、axios.get发出一个get请求(产品图纸设计)----->Axios.js
2、图纸给到工厂----->Axios.js(request方法)
3、分配流水线---->Axios.js(request方法)
4、(启动引擎adapter)产品制作---->dispatchRequest.js(dispatchRequest方法)
5、产品生产完毕----->dispatchRequest.js(dispatchRequest方法)
6、交互

到这里axios的主要源码我们算是已经分析完毕了,其它的一些(拦截器、node部分、文件流处理等等)我就不带着一起研究了哈,留个小伙伴自己慢慢看咯,我们接下来看一下axios的cancle机制(终止网络请求)

在官方文档中我们可以看到,如果我们要终止一个网络请求,我们需要执行:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

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

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

为什么要重点说一下axios的cancel呢?因为我觉得它这个实现机制还是挺不错的,至少我之前没这么写过(哈哈~~承认自己比较菜)

在说axios的cancel之前如果我们在一个普通的ajax请求,我们需要终止一个请求该这么办呢? 相信接触过ajax的小伙伴应该都知道,我们只需要执行xmlhttprequest对象的abort方法就OK了,所以我们直接反着来分析axios源码,我们直接找到xhr.js中的abort方法:
xhr.js

module.exports = function xhrAdapter(config) {
  	.....
    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }
        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }
......
};

果然,跟我们的猜测一样,最后还是调用了xmlhttprequest对象的abort方法。

我们来用一用:

created () {
      const CancelToken = axios.CancelToken
      const source = CancelToken.source()
      axios.get('http://localhost:8090/home',{cancelToken: source.token}).then((res) => {
        console.log(res)
      }).catch((erro) => {
        console.log(erro)
      })
      source.cancel('看你不爽')
    }

可以看到,我们直接在创建了一个网络请求后直接关闭,我们运行看一下结果:
在这里插入图片描述

我们一步一步的来分析:
首先我们获取了axios中的CancelToken函数:

 const CancelToken = axios.CancelToken

CancelToken.js:

'use strict';

var Cancel = require('./Cancel');

/**
 * A `CancelToken` is an object that can be used to request cancellation of an operation.
 *
 * @class
 * @param {Function} executor The executor function.
 */
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;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

/**
 * Throws a `Cancel` if cancellation has been requested.
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

/**
 * Returns an object that contains a new `CancelToken` and a function that, when called,
 * cancels the `CancelToken`.
 */
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;

代码不算多~~

我们接着直接执行了CancelToken的source方法:

created () {
      const CancelToken = axios.CancelToken
      const source = CancelToken.source()
      axios.get('http://localhost:8090/home',{cancelToken: source.token}).then((res) => {
        console.log(res)
      }).catch((erro) => {
        console.log(erro)
      })
      source.cancel('看你不爽')
    }
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

可以看到,在CancelToken的source方法中,直接创建了一个CancelToken对象,然后在返回的对象中包含了token对象跟cancel方法:

  return {
    token: token,
    cancel: cancel
  };

我们首先看一下CancelToken的构造函数:

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;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

在CancelToken的构造函数中我们发现,创建一个promise对象,然后把promise对象的resolve方法传给了一个叫cancel的方法,在cancel方法中执行了resolve方法,最后把promise给了当前this(也就是返回的token对象)。

在demo中我们可以看到:

created () {
      const CancelToken = axios.CancelToken
      const source = CancelToken.source()
      axios.get('http://localhost:8090/home',{cancelToken: source.token}).then((res) => {
        console.log(res)
      }).catch((erro) => {
        console.log(erro)
      })
      source.cancel('看你不爽')
    }

我们的cancel方法执行后请求就终止了:

 source.cancel('看你不爽')

那最后返回的token对象到哪去了呢?在demo中可以看到,token对象最后作为config传递给了axios对象:

axios.get('http://localhost:8090/home',{cancelToken: source.token})

然后token对象进过流水线一层一层的最后被传给了xmlhttprequest对象(也就是我们最开始说的地方):
xhr.js


    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

所以当我们执行cancel方法的时候:

HelloWorld.vue

  source.cancel('看你不爽')

往下走CancelToken.js:

 function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  }

我们直接执行了promise的resolve方法,所以会立马执行xhr.js中的:

  if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

最后执行 request.abort();请求被终止。

好了~ axios我们就研究到这里啦 下面总结一下研究的东西:

axios源码中运用了很多种设计模式(工厂设计模式、观察者模式等等)遵守了对扩展开放、对修改关闭的设计思想,使得代码具有高内聚低耦合特性(看着很牛掰、很爽!!!),最后还是感觉框架其实实现的功能很简单,但是之所以称为框架是因为其设计思想能够被大多数人所接受,思路比较清晰,说白了就是“别人代码写得比你牛逼!!!”哈哈哈~~

先到这里啦,欢迎志同道合的小伙伴入群,一起交流一起学习~~ 加油骚年!!
qq群链接:

在这里插入图片描述

vv_小虫 CSDN认证博客专家 JavaScript CSS 前端框架
6 年开发经验,前端架构师,目前主要负责企业级应用前端技术平台建设工作,在前端工程化实现、Node 应用开发、Android 技术、Vue 技术、React 技术、移动开发等方向有丰富实践。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页