自己封装一个mini版的axios,支持Promise 调用

244 阅读3分钟

axios源码分析

  • axios的入口是在 lib/ axios.js

    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 exportedvar axios = createInstance(defaults);// Expose Axios class to allow class inheritanceaxios.Axios = Axios;// Factory for creating new instancesaxios.create = function create(instanceConfig) {  return createInstance(utils.merge(defaults, instanceConfig));};

  • 当我们在调用axios.create({})方法的时候,会去调用createInstance方法,这个方法中接收一个合并配置的参数,(在axios中会有一些默认的配置,像headers,请求参数的序列化,timeout,还有最重要的就是XMLHttpRequest实例方法)最后这个方法return 一个instance。这个instance就是函数中调用Axios的request方法,最后返回的一个实例。 这个函数最终调用的就是Axios.request
  • 让我们找到Axios.request的方法 core/Axios.js

    Axios.prototype.request = function request(config) {  // Allow for axios('example/url'[, config]) a la fetch API  if (typeof config === 'string') {    config = utils.merge({      url: arguments[0]    }, arguments[1]);  }  config = utils.merge(defaults, this.defaults, { method: 'get' }, config);  config.method = config.method.toLowerCase();  // Support baseURL config  if (config.baseURL && !isAbsoluteURL(config.url)) {    config.url = combineURLs(config.baseURL, config.url);  }  // 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;};
  • 这个方法中的参数config就是上面合并了所有配置的config。如果config是个字符串那么会把URL单独拿出来 和其他参数做一次合并。关键逻辑:var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); }return promise; 这里就是最终去执行了dispatchRequest(config)这个方法。
  • 来看下dispatchRequest  core/dispatchRequest.js

    module.exports = function dispatchRequest(config) {  throwIfCancellationRequested(config);  // 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;//这里的adapter 是从defaults.js中合并过来的  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);  });};

  • 这个方法一进来的是也是一样 合并处理。关于合并处理 其实有很多东西可以讲 这里为了节省时间 不再细讲。感兴趣的朋友可以翻看源码看一下。那么这个方法中最关键的就是var adapter = config.adapter || defaults.adapter;  return adapter(config)。 这个adapter其实就是从defaults中拿过来的,它是我们能够实现Ajax请求 并使用Promise链式调用的关键逻辑。
  • adapter   lib/defaults.js

    
    function getDefaultAdapter() {  var adapter;  if (typeof XMLHttpRequest !== 'undefined') {    // For browsers use XHR adapter    adapter = require('./adapters/xhr');  } else if (typeof process !== 'undefined') {    // For node use HTTP adapter    adapter = require('./adapters/http');  }  return adapter;}
    var defaults = {  adapter: getDefaultAdapter(),  transformRequest: [function transformRequest(data, headers) {    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;  }],  timeout: 0,  xsrfCookieName: 'XSRF-TOKEN',  xsrfHeaderName: 'X-XSRF-TOKEN',  maxContentLength: -1,  validateStatus: function validateStatus(status) {    return status >= 200 && status < 300;  }};

  • 上面我们只需要关注  adapter: getDefaultAdapter(),即可。 getDefaultAdapter 这个方法 adapter = require('./adapters/xhr'); 最终从xhr这个js中拿到了一个 new promise() 里面写了XMLHttpRequest 实例的方法和配置。 到这也就明白了axios是怎么实现promise链式调用的。

知道了原理以后我们可以试着自己写一个。

/* index.js */
import instance from "./defaults";function Ask(options) { return instance(options);}export default Ask;

/**defaults.js */

import { buildUrl } from "./utils.js";export default function(options) {  return new Promise((reslove, reject) => {    let requestData = options.data;    let requestHeaders = options.headers;    let xhr = new XMLHttpRequest();    xhr.open(      options.method.toUpperCase(),      buildUrl(options.url, options.params)    );    xhr.timeout = options.timeout;    xhr.onreadystatechange = function statechange() {      if (xhr.readyState !== 4) {        return;      }      let responseData = {        data: JSON.parse(xhr.response),        status: xhr.status      };      console.log(responseData, 'responseData')      if (        !xhr.status ||        (xhr.status >= 200 && xhr.status < 300) ||        xhr.status === 304      ) {        reslove(responseData);      } else {        reject("request error");      }    };    xhr.ontimeout = function timeout() {      reject("error time out");    };    if ("setRequestHeader" in xhr) {      for (let k in requestHeaders) {        if (requestHeaders.hasOwnProperty(k)) {          let v = requestHeaders[k];          xhr.setRequestHeader(k, v);        }      }    }    if (requestData === undefined) {      requestData = null;    }    xhr.send(requestData);  });}

/** utils.js*/
export const buildUrl = (url, params) => {  if (!params) {    return url;  }  let parts = [];  let serializedParams;  for (let key in params) {    if (params.hasOwnProperty(key)) {      let str = encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);      parts.push(str);    }  }  if (parts.length) {    serializedParams = parts.join("&");    url += (url.indexOf("?") === -1 ? "?" : "&") + serializedParams;    return url;  }};
  • 目前只是实现了一些基本的功能。像axios.all  axios.get  axios.post 等等这些功能还有待完善 但是我们只要理解了原理以后 再去写功能 就是灰常轻松愉快的事情了。 

 欢迎大佬多多指教  不足之处 还望轻喷!