ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Implementation Axios

2022-02-14 18:36:00  阅读:255  来源: 互联网

标签:function axios return Implementation request Axios config response


同步链接: https://www.shanejix.com/posts/Implementation Axios/

曾经想过实现一个 mini 版的 axios,终于达成目标了!

mini axios

const xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
  if (request.readyState === 4) {
    if (request.status === 200) {
      return success(request.responseText);
    } else {
      return fail(request.status);
    }
  } else {
  }
};

xhr.open(method, url, true);

xhr.send(body);

也许你会捂不住嘴直呼这 tm 不是 Ajax(Async JavaScript And XML)吗~,跟 axios 有毛关系。

当然,如果没看过 axios 源码,确实很难让 axios 的浏览器实现和 Ajax 扯上联系,axios 不仅包装了 XMLHttpRequest,而且还很彻底,多彻底呢?

XMLHttpRequest 的属性:

  • onreadystatechange
  • readyState
  • response
  • responseText
  • responseType
  • responseURL
  • responseXML
  • status
  • statusText
  • timeout
  • upload
  • withCredentials

XMLHttpRequest 的方法:

  • abort()
  • getAllResponseHeaders()
  • getResponseHeader()
  • open()
  • openRequest()
  • overrideMimeType()
  • send()
  • setRequestHeader()

能用上的几乎都用上了!一览无余,是否有似曾相识的感觉!在没有 axios 的时代,可是手撸 http 请求的呢(得瑟)。怎么,还是还是觉得太空旷难以和 axios 的使用或实现建立联系?别慌!慢慢来,让我们从 axios 的使用和功能慢慢回忆。

Axios

首先需要知道 axios 是一个基于 Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生 XHR 的封装,只不过它是 Promise 的实现版本,有以下特点:

  • 在浏览器端使用 XMLHttpRequest 对象通讯
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 支持请求和响应的拦截器
  • 支持请求数据和响应数据的转换
  • 支持请求的取消
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

看了一大堆不如 demo 来得直接:

超级简单的

axios("/user?ID=12345").then((res) => {
  console.log(res);
});

几乎涵盖所有所有用法


// Set config defaults when creating the instance
const instance = axios.create(config);

// Append interceptors with instance
const myInterceptor = instance.interceptors.request.use(function () {/*...*/ });
instance.interceptors.request.eject(myInterceptor);

// Alter defaults after instance has been created
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;

// Alter properties after alert defaults in request
instance.get('/longRequest', {
  timeout: 5000,
  ...
}).then((res: response) => { });
// config
const config = {
  url: "/xxx",
  method: "get", // default
  baseURL: "https://some-domain.com/api/",
  transformRequest: [
    function (data, headers) {
      return data;
    },
  ],
  transformResponse: [
    function (data) {
      return data;
    },
  ],
  headers: { "X-Requested-With": "XMLHttpRequest" },
  params: {},
  paramsSerializer: function (params) {
    return Qs.stringify(params, { arrayFormat: "brackets" });
  },
  data: {},
  timeout: 1000,
  withCredentials: false, // default
  adapter: function (config) {},
  auth: {},
  responseType: "json", // default
  responseEncoding: "utf8", // default
  xsrfCookieName: "XSRF-TOKEN", // default
  xsrfHeaderName: "X-XSRF-TOKEN", // default
  onUploadProgress: function (progressEvent) {},
  onDownloadProgress: function (progressEvent) {},
  maxContentLength: 2000,
  validateStatus: function (status) {
    return status >= 200 && status < 300; // default
  },
  maxRedirects: 5, // default
  socketPath: null, // default
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),
  proxy: {},
  cancelToken: new CancelToken(function (cancel) {}),
};

// response model
interface response {
  data: {};
  status: 200;
  statusText: "OK";
  headers: {};
  config: {};
  request: {};
}

上述 instance 实例对于 axios 同样适用,有没有发现很多属性和方法 XMLHttpRequest 中有 axios 中同样也有呢?

没错 axios 中大部分的核心功能就是基于此的,下面看看是怎么实现 axios 的核心功能的吧!

核心实现

首先,用 create-react-app 创建了个简单的 demo 用于模拟查看 axios 的具体调用逻辑

useEffect(() => {
  debugger;
  Axios.get("www.biying.com").then((res) => {
    console.log(res);
  });
}, []);

demo 中 get 请求的调用栈如下图

大致可以分为三个阶段:

merge config

左边的小红框

transform

转换各种 data

request

依据 adapter 发送真实请求

当然这只是宏观上的认识,具体实现还得从源码入手

Index

https://github.com/axios/axios/blob/master/index.js

入口直接 require 到 lib 目录下

module.exports = require("./lib/axios");

axios

https://github.com/axios/axios/blob/master/lib/axios.js

直接导出 axios

module.exports = axios;

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

并且,导出的 axios 是默认配置 defaults 的实例对象

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

然后对 axios 对象做了扩展,create 方法,Axios 类,CancelToken 等等

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

createInstance:

创建 axios 实例,并绑定 context 上下文

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

defaults:

https://github.com/axios/axios/blob/master/lib/defaults.js

默认配置中包含适配器(根据环境决定用什么发送请求),发送请求前对 data 和 headers 的转换函数,接受请求后对 data 的转换函数等

var defaults = {
  adapter: getDefaultAdapter(),

  transformRequest: [function transformRequest(data, headers) {}],

  transformResponse: [function transformResponse(data) {}],

  timeout: 0,

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

  maxContentLength: -1,
  maxBodyLength: -1,

  validateStatus: function validateStatus(status) {},
};

defaults.headers = {
  common: {},
};

Axios

https://github.com/axios/axios/blob/master/lib/core/Axios.js

Axios():

构造函数初始化 defauls 属性和请求响应拦截器

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

request(核心):

request() 方法实际执行 dispatchRequest 时会将请求拦截和响应拦截中加入 chain 队列的两端,从而实现一个promise 调用链

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  ...

  // Set config.method
  ...

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

InterceptorManager

(请求和响应)拦截器其实就是基于队列,实现的一个发布订阅模型

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

订阅:入栈的对象的两个 key 所对应的 value 分别对应 promise 中的 resoveleFn 和 rejectedFn 回调

/**
 * 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
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
  });
  return this.handlers.length - 1;
};

发布:遍历 handlers 数组中个每个 item,在 request()中会加入 chain 的两端

/**
 * Iterate over all the registered interceptors
 *
 * This method is particularly useful for skipping over any
 * interceptors that may have become `null` calling `eject`.
 *
 * @param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

取消:对应位置设置为 null(不是直接删除这个位置的元素),chain 链中不会执行

/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

dispatchRequest:

https://github.com/axios/axios/blob/master/lib/core/dispatchRequest.js

核心逻辑就是,调用 adapter 执行正真的请求

/**
 * 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) {

  // Ensure headers exist
  ...
  // Transform request data
  ...
  // Flatten headers
  ...

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

  return adapter(config).then(function onAdapterResolution(response) {
    ...

    // Transform response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  },
  function onAdapterRejection(reason) {
    ...
    // Transform response data
    if (reason && reason.response) {
      reason.response.data = transformData(
        reason.response.data,
        reason.response.headers,
        config.transformResponse
      );
    }

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

getDefaultAdapter()

简单直接,对浏览器端和 node 判断

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== "undefined") {
    // For browsers use XHR adapter
    adapter = require("./adapters/xhr");
  } else if (
    typeof process !== "undefined" &&
    Object.prototype.toString.call(process) === "[object process]"
  ) {
    // For node use HTTP adapter
    adapter = require("./adapters/http");
  }
  return adapter;
}

xhrAdapter:

https://github.com/axios/axios/blob/master/lib/adapters/xhr.js

返回一个 promise,核心逻辑还是对 XMLHttpRequest 的运用,是不是和开篇殊途同归呢

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    ...

    var request = new XMLHttpRequest();

    // HTTP basic authentication
    ...

    // 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 one rror 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 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
      // one rror 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() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, 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.
    ...

    // Add headers to the request
    if ('setRequestHeader' in request) {
      ...
    }

    // Add withCredentials to request if needed
    ...

    // Add responseType to request if needed
    ...
    // Handle progress if needed
    ...

    // Not all browsers support upload events
    ...

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

总结

沿着本文的思路顺便画了张图

references

作者:shanejix
出处:https://www.shanejix.com/posts/Implementation Axios/
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
声明:转载请注明出处!

标签:function,axios,return,Implementation,request,Axios,config,response
来源: https://www.cnblogs.com/shanejix/p/15893681.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有