axios源码浅分析

14 篇文章 0 订阅
5 篇文章 0 订阅

特点

  • 基于xhr(node)+Promise的异步ajax请求库
  • 浏览器或者node都能使用
  • 支持请求/响应拦截器
  • 支持取消请求

axios.create(config)

  • 根据配置返回一个新的axios
  • 新返回来的axios没有取消请求合批量发送请求的方法,其他语法一致

拦截器调用顺序

  • 说明:调用axios()并不是立即发送请求的,而是经历一个较长的流程
  • 流程:请求拦截器2->请求拦截器1->发送ajax请求->响应拦截器1->响应拦截器2->请求回调
  • 流程是使用Promise串联起来的,请求拦截器传递的是config,响应拦截器传递的是response

取消请求

  • 配置cancelToken对象
  • 缓存用来取消请求的cancel函数
  • 在需要的时候调用cancel函数取消请求
  • 内部会让cancelPromise变为成功,并且成功值为一个Cancel对象
  • 在cancelPromise的成功回调中中断请求,并让发送请求的Promise失败

axios与Axios关系

  • 语法上:axios不是Axios实例
  • 功能上:axios是Axios实例
  • axios是Axios.prototype.request函数bind()返回的函数
  • axios作为对象有Axios原型对象上的所有方法,有Axios对象上所有属性

instance与axios的区别

  • 相同
    • 都是能否发送任意请求的函数:request(config)
    • 都能发送特定请求的方法:get()/post()
    • 都有默认配置和拦截器属性:default/interceptors
  • 不同
    • 默认配置可能不一样
    • instance没有axios后面添加的一些方法:create()/CancelToken()/all()

请求/响应数据转换器是什么

  • 请求转换器是对请求头和请求体数据进行特定处理的函数
  • 响应转换器是将响应体json字符串解析为js对象或者数组的函数

axios 对象创建过程代码

  • 创建一个Axios实例
  • 利用bind方法将Axios.prototype.request函数中的this绑定为创建出来的实例,并返回一个新的函数
  • Axios.prototype中的方法挂载到新返回来的函数中,同时需要绑定this为创建出来的实例
  • 将实例中的属性挂载到新返回来的函数中
function Axios(defaultConfig) {
  // 默认配置
  this.defaultConfig = defaultConfig;
  // 拦截器
  this.interceptor = {
    request: {},
    respond: {}
  };
}

Axios.prototype.request = function(config = {}) {
  console.log("发送请求:" + config.method + "请求");
};
Axios.prototype.get = function(config = {}) {
  return this.request({
    method: "GET",
    ...config
  });
};
Axios.prototype.post = function(config = {}) {
  return this.request({
    method: "POST",
    ...config
  });
};

function createInstance(config) {
  const context = new Axios(config);
  // 创建请求函数
  const instance = Axios.prototype.request.bind(context);

  // 将Axios.prototype中的get,post等方法挂载到instance请求函数中
  Object.keys(Axios.prototype).forEach(key => {
    instance[key] = Axios.prototype[key].bind(context);
  });
  // 将context中的defaultConfig和interceptor挂在到instance请求函数中
  Object.keys(context).forEach(key => {
    instance[key] = context[key];
  });
  return instance;
}

const axios = createInstance();

axios 请求过程代码

  • dispatchRequest函数选择合适的适配器去发送请求,web端或者node端
function Axios(defaultConfig) {
  // 默认配置
  this.defaultConfig = defaultConfig;
}

Axios.prototype.request = function(config = {}) {
  console.log("发送请求:" + config.method + "请求");
  const promise = Promise.resolve(config);
  // undefined是用来占位的,拦截器会用到
  const chain = [dispatchRequest, undefined];
  const result = promise.then(chain[0], chain[1]);
  return result;
};

function dispatchRequest(config) {
  // 调用适配器发送请求,调用浏览器端的或者node端的
  return xhrAdapter(config)
    .then(res => {
      // 此处格式化返回来的数据
      // ...
      return res;
    })
    .catch(error => {
      throw error;
    });
}
function xhrAdapter(config) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(config.method.toUpperCase(), config.url);
    xhr.send();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve({
            config,
            data: xhr.responseText,
            headers: xhr.getAllResponseHeaders(),
            request: xhr,
            status: xhr.status,
            statusText: xhr.statusText
          });
        } else {
          reject(new Error(`请求失败,失败状态码为${xhr.status}`));
        }
      }
    };
  });
}

function createInstance(config) {
  const context = new Axios(config);
  const instance = Axios.prototype.request.bind(context);
  return instance;
}

const axios = createInstance();

axios({
  method: "get",
  url: "xxxxx"
})
  .then(res => {
    console.log("请求成功", res);
  })
  .catch(error => {
    console.log("请求失败", error);
  });

axios 拦截器代码

  • InterceptorManage拦截器管理类有一个use方法用来收集拦截器
  • Axios.prototype.request中有一个chain调用链
  • 请求的时候,将请求拦截器放置chain前面,将相应拦截器放置chain后面,所以请求拦截器后面的先执行,响应拦截器前面的先执行
  • 利用promise链式调用chain中的方法
function InterceptorManage() {
  this.handlers = [];
}

InterceptorManage.prototype.use = function(fulfilled, rejected) {
  this.handlers.push({
    fulfilled,
    rejected
  });
};
function Axios(defaultConfig) {
  // 默认配置
  this.defaultConfig = defaultConfig;
  this.interceptor = {
    request: new InterceptorManage(),
    response: new InterceptorManage()
  };
}

Axios.prototype.request = function(config = {}) {
  let promise = Promise.resolve(config);
  // undefined是用来占位的
  const chain = [dispatchRequest, undefined];
//   将请求拦截器放置到调用链中
  this.interceptor.request.handlers.forEach(handler => {
    chain.unshift(handler.fulfilled, handler.rejected);
  });
//   将响应拦截器放置到调用链中
  this.interceptor.response.handlers.forEach(handler => {
    chain.push(handler.fulfilled, handler.rejected);
  });
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  return promise;
};

axios 取消请求代码

  • 新建一个CancelToken实例,该实例上面挂载了一个promise实例,同时将resolve方法暴露给外面
  • 将实例传递给axios,axios发送请求的时候回调CancelToken实例中的promise。
  • 外界调用resolve方法,就会中断请求
function Axios(defaultConfig) {
  // 默认配置
  this.defaultConfig = defaultConfig;
}

Axios.prototype.request = function(config = {}) {
  const promise = Promise.resolve(config);
  // undefined是用来占位的,拦截器会用到
  const chain = [dispatchRequest, undefined];
  const result = promise.then(chain[0], chain[1]);
  return result;
};

function dispatchRequest(config) {
  // 调用适配器发送请求,调用浏览器端的或者node端的
  return xhrAdapter(config)
    .then(res => {
      // 此处格式化返回来的数据
      // ...
      return res;
    })
    .catch(error => {
      throw error;
    });
}
function xhrAdapter(config) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(config.method.toUpperCase(), config.url);
    xhr.send();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve({
            config,
            data: xhr.responseText,
            headers: xhr.getAllResponseHeaders(),
            request: xhr,
            status: xhr.status,
            statusText: xhr.statusText
          });
        } else {
          reject(new Error(`请求失败,失败状态码为${xhr.status}`));
        }
      }
    };
    if (config.cancelToken) {
      config.cancelToken.promise.then(() => {
        //   当cancelToken实例中的Promise变为成功状态就会中断请求
        xhr.abort();
        reject(new Error("请求中断"));
      });
    }
  });
}

function createInstance(config) {
  const context = new Axios(config);
  const instance = Axios.prototype.request.bind(context);
  return instance;
}

const axios = createInstance();

function CancelToken(executor) {
  let resolvePromise;
  this.promise = new Promise(function(resolve, reject) {
    resolvePromise = resolve;
  });

  executor(function() {
    if (resolvePromise) {
      resolvePromise();
    }
  });
}

let cancel;

document.getElementById("emit").addEventListener("click", function() {
  const cancelToken = new CancelToken(function(c) {
    cancel = c;
  });
  axios({
    method: "get",
    url: "xxx",
    cancelToken
  })
    .then(res => {
      console.log("请求成功", res);
    })
    .catch(error => {
      console.log("请求失败", error);
    });
});

document.getElementById("off").addEventListener("click", function() {
  if (cancel) {
    //   取消请求
    cancel();
  }
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值