龙空技术网

axios经常封装使用 你了解axios的原理吗 axios源码解析

小焱2018 840

前言:

现在朋友们对“jquery相对路径转换绝对路径是什么”大概比较珍视,各位老铁们都需要分析一些“jquery相对路径转换绝对路径是什么”的相关文章。那么小编同时在网摘上收集了一些对于“jquery相对路径转换绝对路径是什么””的相关知识,希望姐妹们能喜欢,姐妹们快快来学习一下吧!

一、axios的使用

安装:

npm install axios

使用:

//引入axiosconst axios = require('axios');import axios from 'axios';

关于axios的基本使用,上篇文章已经有所涉及,这里再稍微回顾下:

发送请求

import axios from 'axios';axios(config) // 直接传入配置axios(url[, config]) // 传入url和配置axios[method](url[, option]) // 直接调用请求方式方法,传入url和配置axios[method](url[, data[, option]]) // 直接调用请求方式方法,传入data、url和配置axios.request(option) // 调用 request 方法const axiosInstance = axios.create(config)// axiosInstance 也具有以上 axios 的能力axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))// 调用 all 和传入 spread 回调

拦截器,类似于中间件的概念,是axios的核心功能之一,主要是分为两种:请求拦截器和响应拦截器。有了拦截器,我们能在网络请求之前,对网络请求配置做出处理。在返回数据之前,对返回的数据做处理。

中间件,拦截器:一般用于对一个目标方法的前置或后置切片操作,可以将一些额外的脏逻辑写到其他的文件中管理,提高目标方法的简洁性。

请求拦截器

axios.interceptors.request.use(function (config) {    // 这里写发送请求前处理的代码    return config;}, function (error) {    // 这里写发送请求错误相关的代码    return Promise.reject(error);});

响应拦截器

axios.interceptors.response.use(function (response) {    // 这里写得到响应数据后处理的代码    return response;}, function (error) {    // 这里写得到错误响应处理的代码    return Promise.reject(error);});

取消请求

// 方式一const CancelToken = axios.CancelToken;const source = CancelToken.source();axios.get('xxxx', {  cancelToken: source.token})// 取消请求 (请求原因是可选的)source.cancel('主动取消请求');// 方式二const CancelToken = axios.CancelToken;let cancel;axios.get('xxxx', {  cancelToken: new CancelToken(function executor(c) {    cancel = c;  })});cancel('主动取消请求');
默认配置

请求配置可以在每个请求中单独设置,也可以在defaults中为全局设置。

//默认baseUrlaxios.defaults.baseUrl = ';;//默认超时时间axios.defaults.timeout = 3000;//默认Authorization头axios.defaults.headers.common['Authorization'] = 'AUTH_TOKEN';
数据转换请求数据转换
axios.defaults.transformRequest.push((data, headers)=>{    //处理请求的data    return data;});
返回数据转换
axios.defaults.transformResponse.push((data, headers)=>{    //处理返回的data    return data;});
请求配置

在上一步的发起请求的方法中,我们都能看到config这个配置参数,通过设置这个参数的值,可以达到配置请求的目的。在axios中,config是沟通调用方和网络库的桥梁,

常用的配置项如下所示:

{  // `url` 是用于请求的服务器 URL,相对路径/绝对路径 url: '/api/users', // `method` 是创建请求时使用的http方法,包括get, post, put, delete等 method: 'get', // default // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。 // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL baseURL: ';, // `transformRequest` 允许在向服务器发送前,修改请求数据 // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法 // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream transformRequest: [function (data, headers) {   // 对 data 进行任意转换处理   return data; }], // `transformResponse` 在传递给 then/catch 前,允许修改响应数据 transformResponse: [function (data) {   // 对 data 进行任意转换处理   return data; }], // `headers` 是即将被发送的自定义请求头 headers: {'X-Requested-With': 'XMLHttpRequest'}, // `params` 是即将与请求一起发送的 URL 参数 // 必须是一个无格式对象(plain object)或 URLSearchParams 对象 params: {   name: 'John' },  // `paramsSerializer` 是一个负责 `params` 序列化的函数 // (e.g. , ) paramsSerializer: function(params) {   return Qs.stringify(params, {arrayFormat: 'brackets'}) }, // `data` 是作为请求主体被发送的数据 // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH' // 在没有设置 `transformRequest` 时,必须是以下类型之一: // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams // - 浏览器专属:FormData, File, Blob // - Node 专属: Stream data: {   firstName: 'John' }, // `timeout` 指定请求超时的毫秒数(0 表示无超时时间) // 如果请求花费了超过 `timeout` 的时间,请求将被中断 timeout: 1000, // `adapter` 允许自定义处理请求,以使测试更轻松 // 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)). adapter: function (config) {   /* ... */ },// `auth` 表示应该使用 HTTP 基础验证,并提供凭据 // 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头 auth: {   username: 'janedoe',   password: 's00pers3cret' },  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream' responseType: 'json', // default // `responseEncoding` 表示用于响应数据的解码方式  responseEncoding: 'utf8', // default // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte validateStatus: function (status) {   return status >= 200 && status < 300; // default },  // `cancelToken` 指定用于取消请求的 cancel token cancelToken: new CancelToken(function (cancel) { }), ...}JAVASCRIPT 复制 全屏

二、实现一个简易版axios

构建一个Axios构造函数,核心代码为request

class Axios {    constructor() {    }    request(config) {        return new Promise(resolve => {            const {url = '', method = 'get', data = {}} = config;            // 发送ajax请求            const xhr = new XMLHttpRequest();            xhr.open(method, url, true);            xhr.onload = function() {                console.log(xhr.responseText)                resolve(xhr.responseText);            }            xhr.send(data);        })    }}

导出axios实例

// 最终导出axios的方法,即实例的request方法function CreateAxiosFn() {    let axios = new Axios();    let req = axios.request.bind(axios);    return req;}// 得到最后的全局变量axioslet axios = CreateAxiosFn();

上述就已经能够实现axios({ })这种方式的请求

下面是来实现下axios.method()这种形式的请求

// 定义get,post...方法,挂在到Axios原型上const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];methodsArr.forEach(met => {    Axios.prototype[met] = function() {        console.log('执行'+met+'方法');        // 处理单个方法        if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])            return this.request({                method: met,                url: arguments[0],                ...arguments[1] || {}            })        } else { // 3个参数(url[,data[,config]])            return this.request({                method: met,                url: arguments[0],                data: arguments[1] || {},                ...arguments[2] || {}            })        }    }})

Axios.prototype上的方法搬运到request

首先实现个工具类,实现将b方法混入到a,并且修改this指向

const utils = {  extend(a,b, context) {    for(let key in b) {      if (b.hasOwnProperty(key)) {        if (typeof b[key] === 'function') {          a[key] = b[key].bind(context);        } else {          a[key] = b[key]        }      }          }  }}

修改导出的方法

function CreateAxiosFn() {  let axios = new Axios();    let req = axios.request.bind(axios);  // 增加代码  utils.extend(req, Axios.prototype, axios)    return req;}

构建拦截器的构造函数

class InterceptorsManage {  constructor() {    this.handlers = [];  }  use(fullfield, rejected) {    this.handlers.push({      fullfield,      rejected    })  }}

实现axios.interceptors.response.useaxios.interceptors.request.use

class Axios {    constructor() {        // 新增代码        this.interceptors = {            request: new InterceptorsManage,            response: new InterceptorsManage        }    }    request(config) { 		...    }}

执行语句axios.interceptors.response.useaxios.interceptors.request.use的时候,实现获取axios实例上的interceptors对象,然后再获取responserequest拦截器,再执行对应的拦截器的use方法

Axios上的方法和属性搬到request过去

function CreateAxiosFn() {  let axios = new Axios();    let req = axios.request.bind(axios);  // 混入方法, 处理axios的request方法,使之拥有get,post...方法  utils.extend(req, Axios.prototype, axios)  // 新增代码  utils.extend(req, axios)  return req;}

现在request也有了interceptors对象,在发送请求的时候,会先获取request拦截器的handlers的方法来执行

首先将执行ajax的请求封装成一个方法

request(config) {    this.sendAjax(config)}sendAjax(config){    return new Promise(resolve => {        const {url = '', method = 'get', data = {}} = config;        // 发送ajax请求        console.log(config);        const xhr = new XMLHttpRequest();        xhr.open(method, url, true);        xhr.onload = function() {            console.log(xhr.responseText)            resolve(xhr.responseText);        };        xhr.send(data);    })}

获得handlers中的回调

request(config) {    // 拦截器和请求组装队列    let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理    // 请求拦截    this.interceptors.request.handlers.forEach(interceptor => {        chain.unshift(interceptor.fullfield, interceptor.rejected)    })    // 响应拦截    this.interceptors.response.handlers.forEach(interceptor => {        chain.push(interceptor.fullfield, interceptor.rejected)    })    // 执行队列,每次执行一对,并给promise赋最新的值    let promise = Promise.resolve(config);    while(chain.length > 0) {        promise = promise.then(chain.shift(), chain.shift())    }    return promise;}

chains大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']这种形式

这样就能够成功实现一个简易版axios

三、源码分析

首先看看目录结构

axios发送请求有很多实现的方法,实现入口文件为axios.js

function createInstance(defaultConfig) {  var context = new Axios(defaultConfig);  // instance指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用   // Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用  var instance = bind(Axios.prototype.request, context);  // 把Axios.prototype上的方法扩展到instance对象上,  // 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context  utils.extend(instance, Axios.prototype, context);  // Copy context to instance  // 把context对象上的自身属性和方法扩展到instance上  // 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性  // 这样,instance 就有了  defaults、interceptors 属性。  utils.extend(instance, context);  return instance;}// Create the default instance to be exported 创建一个由默认配置生成的axios实例var axios = createInstance(defaults);// Factory for creating new instances 扩展axios.create工厂函数,内部也是 createInstanceaxios.create = function create(instanceConfig) {  return createInstance(mergeConfig(axios.defaults, instanceConfig));};// Expose all/spreadaxios.all = function all(promises) {  return Promise.all(promises);};axios.spread = function spread(callback) {  return function wrap(arr) {    return callback.apply(null, arr);  };};module.exports = axios;

主要核心是 Axios.prototype.request,各种请求方式的调用实现都是在 request 内部实现的, 简单看下 request 的逻辑

Axios.prototype.request = function request(config) {  // Allow for axios('example/url'[, config]) a la fetch API  // 判断 config 参数是否是 字符串,如果是则认为第一个参数是 URL,第二个参数是真正的config  if (typeof config === 'string') {    config = arguments[1] || {};    // 把 url 放置到 config 对象中,便于之后的 mergeConfig    config.url = arguments[0];  } else {    // 如果 config 参数是否是 字符串,则整体都当做config    config = config || {};  }  // 合并默认配置和传入的配置  config = mergeConfig(this.defaults, config);  // 设置请求方法  config.method = config.method ? config.method.toLowerCase() : 'get';  /*    something... 此部分会在后续拦截器单独讲述  */};// 在 Axios 原型上挂载 'delete', 'get', 'head', 'options' 且不传参的请求方法,实现内部也是 requestutils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {  Axios.prototype[method] = function(url, config) {    return this.request(utils.merge(config || {}, {      method: method,      url: url    }));  };});// 在 Axios 原型上挂载 'post', 'put', 'patch' 且传参的请求方法,实现内部同样也是 requestutils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {  Axios.prototype[method] = function(url, data, config) {    return this.request(utils.merge(config || {}, {      method: method,      url: url,      data: data    }));  };});

request入口参数为config,可以说config贯彻了axios的一生

axios 中的 config主要分布在这几个地方:

默认配置 defaults.jsconfig.method默认为 get调用 createInstance 方法创建 axios实例,传入的config直接或间接调用 request 方法,传入的 config

// axios.js// 创建一个由默认配置生成的axios实例var axios = createInstance(defaults);// 扩展axios.create工厂函数,内部也是 createInstanceaxios.create = function create(instanceConfig) {  return createInstance(mergeConfig(axios.defaults, instanceConfig));};// Axios.js// 合并默认配置和传入的配置config = mergeConfig(this.defaults, config);// 设置请求方法config.method = config.method ? config.method.toLowerCase() : 'get';

从源码中,可以看到优先级:默认配置对象default < method:get < Axios的实例属性this.default < request参数

下面重点看看request方法

Axios.prototype.request = function request(config) {  /*    先是 mergeConfig ... 等,不再阐述  */  // Hook up interceptors middleware 创建拦截器链. dispatchRequest 是重中之重,后续重点  var chain = [dispatchRequest, undefined];  // push各个拦截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {    // 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法    chain.unshift(interceptor.fulfilled, interceptor.rejected);  });  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {    // 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法    chain.push(interceptor.fulfilled, interceptor.rejected);  });  // 初始化一个promise对象,状态为resolved,接收到的参数为已经处理合并过的config对象  var promise = Promise.resolve(config);  // 循环拦截器的链  while (chain.length) {    promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器  }  // 返回 promise  return promise;};

拦截器interceptors是在构建axios实例化的属性

function Axios(instanceConfig) {  this.defaults = instanceConfig;  this.interceptors = {    request: new InterceptorManager(), // 请求拦截    response: new InterceptorManager() // 响应拦截  };}

InterceptorManager构造函数

// 拦截器的初始化 其实就是一组钩子函数function InterceptorManager() {  this.handlers = [];}// 调用拦截器实例的use时就是往钩子函数中push方法InterceptorManager.prototype.use = function use(fulfilled, rejected) {  this.handlers.push({    fulfilled: fulfilled,    rejected: rejected  });  return this.handlers.length - 1;};// 拦截器是可以取消的,根据use的时候返回的ID,把某一个拦截器方法置为null// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化,导致之后的顺序或者是操作不可控InterceptorManager.prototype.eject = function eject(id) {  if (this.handlers[id]) {    this.handlers[id] = null;  }};// 这就是在 Axios的request方法中 中循环拦截器的方法 forEach 循环执行钩子函数InterceptorManager.prototype.forEach = function forEach(fn) {  utils.forEach(this.handlers, function forEachHandler(h) {    if (h !== null) {      fn(h);    }  });}

请求拦截器方法是被 unshift到拦截器中,响应拦截器是被push到拦截器中的。最终它们会拼接上一个叫dispatchRequest的方法被后续的 promise 顺序执行

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');// 判断请求是否已被取消,如果已经被取消,抛出已取消function throwIfCancellationRequested(config) {  if (config.cancelToken) {    config.cancelToken.throwIfRequested();  }}module.exports = function dispatchRequest(config) {  throwIfCancellationRequested(config);  // 如果包含baseUrl, 并且不是config.url绝对路径,组合baseUrl以及config.url  if (config.baseURL && !isAbsoluteURL(config.url)) {    // 组合baseURL与url形成完整的请求路径    config.url = combineURLs(config.baseURL, config.url);  }  config.headers = config.headers || {};  // 使用/lib/defaults.js中的transformRequest方法,对config.headers和config.data进行格式化  // 比如将headers中的Accept,Content-Type统一处理成大写  // 比如如果请求正文是一个Object会格式化为JSON字符串,并添加application/json;charset=utf-8的Content-Type  // 等一系列操作  config.data = transformData(    config.data,    config.headers,    config.transformRequest  );  // 合并不同配置的headers,config.headers的配置优先级更高  config.headers = utils.merge(    config.headers.common || {},    config.headers[config.method] || {},    config.headers || {}  );  // 删除headers中的method属性  utils.forEach(    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],    function cleanHeaderConfig(method) {      delete config.headers[method];    }  );  // 如果config配置了adapter,使用config中配置adapter的替代默认的请求方法  var adapter = config.adapter || defaults.adapter;  // 使用adapter方法发起请求(adapter根据浏览器环境或者Node环境会有不同)  return adapter(config).then(    // 请求正确返回的回调    function onAdapterResolution(response) {      // 判断是否以及取消了请求,如果取消了请求抛出以取消      throwIfCancellationRequested(config);      // 使用/lib/defaults.js中的transformResponse方法,对服务器返回的数据进行格式化      // 例如,使用JSON.parse对响应正文进行解析      response.data = transformData(        response.data,        response.headers,        config.transformResponse      );      return response;    },    // 请求失败的回调    function onAdapterRejection(reason) {      if (!isCancel(reason)) {        throwIfCancellationRequested(config);        if (reason && reason.response) {          reason.response.data = transformData(            reason.response.data,            reason.response.headers,            config.transformResponse          );        }      }      return Promise.reject(reason);    }  );};

再来看看axios是如何实现取消请求的,实现文件在CancelToken.js

function CancelToken(executor) {  if (typeof executor !== 'function') {    throw new TypeError('executor must be a function.');  }  // 在 CancelToken 上定义一个 pending 状态的 promise ,将 resolve 回调赋值给外部变量 resolvePromise  var resolvePromise;  this.promise = new Promise(function promiseExecutor(resolve) {    resolvePromise = resolve;  });  var token = this;  // 立即执行 传入的 executor函数,将真实的 cancel 方法通过参数传递出去。  // 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve,就更改promise的状态为 resolve。  // 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求  executor(function cancel(message) {    // 判断请求是否已经取消过,避免多次执行    if (token.reason) {      return;    }    token.reason = new Cancel(message);    resolvePromise(token.reason);  });}CancelToken.source = function source() {  // source 方法就是返回了一个 CancelToken 实例,与直接使用 new CancelToken 是一样的操作  var cancel;  var token = new CancelToken(function executor(c) {    cancel = c;  });  // 返回创建的 CancelToken 实例以及取消方法  return {    token: token,    cancel: cancel  };};

实际上取消请求的操作是在 xhr.js 中也有响应的配合的

if (config.cancelToken) {    config.cancelToken.promise.then(function onCanceled(cancel) {        if (!request) {            return;        }        // 取消请求        request.abort();        reject(cancel);    });}

巧妙的地方在 CancelTokenexecutor 函数,通过resolve函数的传递与执行,控制promise的状态

小结

标签: #jquery相对路径转换绝对路径是什么