axios源码主流程分析

65 阅读1分钟

1.概览

image.png 用户实际使用的axios既是一个函数,也是对象。它本身是一个request函数,经过包装之后,身上又具有config和interceptors等诸多属性,这一点其实和jquery是完全一样的。axios支持两种请求,一种是ajax请求(浏览器环境),一种是http请求(node环境),分别对应xhrAdapter和httpAdapter这两个适配器

2.代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>axios</title>
    <link
      crossorigin="anonymous"
      href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
      rel="stylesheet"
    />
  </head>
  <body>
    <div class="container">
      <h2 class="page-header">axios取消请求</h2>
      <button class="btn btn-primary">发送请求</button>
      <button class="btn btn-warning">取消请求</button>
    </div>
    <script>
      function Axios(config) {
        this.config = config;
        this.interceptors = {
          request: new InterceptorManager(),
          response: new InterceptorManager(),
        };
      }
      Axios.prototype.request = function(config) {
        let promise = Promise.resolve(config);
        const chains = [dispatchRequest, undefined];
        this.interceptors.request.handlers.forEach((item) => {
          chains.unshift(item.fulfilled, item.rejected);
        });
        this.interceptors.response.handlers.forEach((item) => {
          chains.push(item.fulfilled, item.rejected);
        });
        while (chains.length > 0) {
          promise = promise.then(chains.shift(), chains.shift());
        }
        return promise;
      };

      function dispatchRequest(config) {
        return xhrAdapter(config);
      }

      function xhrAdapter(config) {
        return new Promise((resolve, reject) => {
          const xhr = new XMLHttpRequest();
          xhr.open(config.method, config.url);
          xhr.send();
          xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
              if (xhr.status >= 200 && xhr.status < 300) {
                resolve({
                  status: xhr.status,
                  statusText: xhr.statusText,
                });
              } else {
                reject(new Error("request error: " + xhr.status));
              }
            }
          };
          if (config.cancelToken) {
            config.cancelToken.promise.then((value) => {
              xhr.abort();
              reject(new Error("request cancel"));
            });
          }
        });
      }
      function CancelToken(executor) {
        let resolvePromise;
        this.promise = new Promise((resolve) => {
          resolvePromise = resolve;
        });
        executor(function() {
          resolvePromise();
        });
      }

      const context = new Axios({});
      const axios = Axios.prototype.request.bind(context);
      console.dir(axios, "axios request");
      //   debugger;
      // 为什么这里还要将context的interceptor属性赋值给axios?
      // 因为bind只是为了让request里的this能访问到interceptor,这里再赋值是为了暴露给用户使用拦截器
      Object.keys(context).forEach((key) => {
        axios[key] = context[key];
      });
      console.dir(axios, "axios request222");

      function InterceptorManager() {
        this.handlers = [];
      }
      InterceptorManager.prototype.use = function(fulfilled, rejected) {
        this.handlers.push({
          fulfilled,
          rejected,
        });
      };

      axios.interceptors.request.use(
        function one(config) {
          console.log("请求拦截器 成功 - 1号");
          return config;
        },
        function one(error) {
          console.log("请求拦截器 失败 - 1号");
          return Promise.reject(error);
        }
      );

      axios.interceptors.request.use(
        function two(config) {
          console.log("请求拦截器 成功 - 2号");
          return config;
        },
        function two(error) {
          console.log("请求拦截器 失败 - 2号");
          return Promise.reject(error);
        }
      );

      // 设置响应拦截器
      axios.interceptors.response.use(
        function(response) {
          console.log("响应拦截器 成功 1号");
          return response;
        },
        function(error) {
          console.log("响应拦截器 失败 1号");
          return Promise.reject(error);
        }
      );

      axios.interceptors.response.use(
        function(response) {
          console.log("响应拦截器 成功 2号");
          return response;
        },
        function(error) {
          console.log("响应拦截器 失败 2号");
          return Promise.reject(error);
        }
      );

      const btns = document.querySelectorAll("button");
      let cancel = null;
      btns[0].onclick = function() {
        if (cancel !== null) {
          cancel();
        }

        let cancelToken = new CancelToken(function(c) {
          cancel = c;
        });

        axios({
          method: "GET",
          url: "http://localhost:3000/posts",
          cancelToken: cancelToken,
        }).then((response) => {
          console.log(response, "response");
          cancel = null;
        });
      };
      btns[1].onclick = function() {
        cancel();
      };
    </script>
  </body>
</html>

3.request

Axios.prototype.request = function(config) {
    let promise = Promise.resolve(config);
    const chains = [dispatchRequest, undefined];
    this.interceptors.request.handlers.forEach((item) => {
      chains.unshift(item.fulfilled, item.rejected);
    });
    this.interceptors.response.handlers.forEach((item) => {
      chains.push(item.fulfilled, item.rejected);
    });
    while (chains.length > 0) {
      promise = promise.then(chains.shift(), chains.shift());
    }
    return promise;
  };

axios源码实现者是一个Promise使用大佬,核心都是Promise。首先创建了一个promise链,初始化是dispatchRequest(发请求)和undefined(占位),之后把请求拦截器的回调在链条前面加入,响应拦截器在链条后面加入,每次执行的过程都是从链条里取出连个回调进行操作,所以之前初始化的时候要有undefined占位,不然执行顺序就错乱了。在promise链执行的过程中,每一次的promise执行结果都会传到下一个执行的promise中。

4.CancelToken

function CancelToken(executor) {
    let resolvePromise;
    this.promise = new Promise((resolve) => {
      resolvePromise = resolve;
    });
    executor(function() {
      resolvePromise();
    });
}
if (config.cancelToken) {
    config.cancelToken.promise.then((value) => {
      xhr.abort();
      reject(new Error("request cancel"));
    });
}

CancelToken本身创建了一个Promise,由用户通过参数传到发送ajax请求里,并在里头写入取消请求的逻辑,一旦用户通过暴露的resolvePromsie改变CancelToken里promise的状态,取消的逻辑便会执行