vue自2.0开始,vue-resource不再作为官方推荐的ajax方案,转而推荐使用axios

按照作者的原话来说:

“Ajax 本身跟 Vue 并没有什么需要特别整合的地方,使用 fetch polyfill 或是 axios、superagent 等等都可以起到同等的效果,vue-resource 提供的价值和其维护成本相比并不划算,所以决定在不久以后取消对 vue-resource 的官方推荐。已有的用户可以继续使用,但以后不再把 vue-resource 作为官方的 ajax 方案。”

除了维护成本方面的原因,axios本身的优点也使得它在一众ajax异步请求的框架中脱颖而出,下面我们通过分析部分axios的源码来看看,是什么让axios成为大多数人的选择。

GitHub上axios的主页标注了它具有如下特性:

Make XMLHttpRequests from the browser

Make http requests from node.js

Supports the Promise API

Intercept request and response

Transform request and response data

Cancel requests

Automatic transforms for JSON data

Client side support for protecting against XSRF

我们来一一分析。

同时支持浏览器端和服务端的请求。

由于axios的这一特性,vue的服务端渲染对于axios简直毫无抵抗力。 让我们一起来读读源码,看看它是如何实现的。

axios/lib/core/dispatchRequest.js文件中暴露的dispatchRequest方法就是axios发送请求的方法,其中有一段代码为:

  1. //定义适配器,判断是在服务器环境还是浏览器环境

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

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

  4.    throwIfCancellationRequested(config);

  5.    // 处理返回的数据

  6.    response.data = transformData(

  7.          response.data,

  8.          response.headers,

  9.         config.transformResponse

  10.    );

  11.    return response;

  12.  }, function onAdapterRejection(reason) {

  13.    if (!isCancel(reason)) {

  14.          throwIfCancellationRequested(config);

  15.      // 处理失败原因

  16.      if (reason && reason.response) {

  17.        reason.response.data = transformData(

  18.              reason.response.data,

  19.              reason.response.headers,

  20.              config.transformResponse

  21.        );

  22.      }

  23.    }

  24.    return Promise.reject(reason);

  25.  });

  26. };

这段代码首先定义了一个适配器,然后返回了适配器处理后的内容。 如果没有在传入的配置参数中指定适配器,则取默认配置文件中定义的适配器,再让我们来看看默认文件/lib/defaults.js定义的适配器:

  1. function getDefaultAdapter() {

  2.  var adapter;

  3.  if (typeof XMLHttpRequest !== 'undefined') {

  4.    //通过判断XMLHttpRequest是否存在,来判断是否是浏览器环境

  5.    adapter = require('./adapters/xhr');

  6.  } else if (typeof process !== 'undefined') {

  7.    //通过判断process是否存在,来判断是否是node环境

  8.    adapter = require('./adapters/http');

  9.  }

  10.  return adapter;

  11. }

到这里真相大白,XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能;process 对象是一个 global (全局变量),提供有关信息,控制当前 Node.js 进程。原来作者是通过判断XMLHttpRequest和process这两个全局变量来判断程序的运行环境的,从而在不同的环境提供不同的http请求模块,实现客户端和服务端程序的兼容

同理,我们在做ssr服务端渲染时,也可以使用这个方法来判断代码当前的执行环境。

支持promise

  1. /**

  2. * 处理一个请求

  3. *

  4. * @param config 请求的配置

  5. */

  6. Axios.prototype.request = function request(config) {

  7.  // 如果是字符串,则直接赋值给配置的url属性

  8.  if (typeof config === 'string') {

  9.    config = utils.merge({

  10.      url: arguments[0]

  11.    }, arguments[1]);

  12.  }

  13.  // 合并默认配置和配置参数    

  14.  config = utils.merge(defaults, this.defaults, { method: 'get' }, config);

  15.  config.method = config.method.toLowerCase();

  16.  // 连接拦截器中间件

  17.  var chain = [dispatchRequest, undefined];

  18.  var promise = Promise.resolve(config);

  19.  //依次在处理链路数组中,从头部添加请求拦截器中间件

  20.  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {

  21.    chain.unshift(interceptor.fulfilled, interceptor.rejected);

  22.  });

  23.  //依次在处理链路数组中,从尾部添加返回拦截器中间件

  24.  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {

  25.    chain.push(interceptor.fulfilled, interceptor.rejected);

  26.  });

  27.  //依次执行 请求拦截器中间件-> 请求 -> 返回拦截器中间件    

  28.  while (chain.length) {

  29.    promise = promise.then(chain.shift(), chain.shift());

  30.  }

  31.  //返回promise对象

  32.  return promise;

  33. };

这一段是axios请求整体流程的核心方法,可以看到请求返回的是一个promise对象。这样可以让我们的异步请求天然的支持promise,方便我们对于异步的处理。

支持请求和和数据返回的拦截

依然是上面的核心流程代码,在设置好请求参数后,作者定义了一个chain数组,同时放入了dispatchRequest, undefined这两个元素对应promise的resolve和reject方法,之后将请求拦截器的成功和失败处理依次压入chain数组头部,将返回拦截器的成功和失败处理依次推入chain数组尾部。 最后循环取出chain数组,先依次取出chain数组中成对的请求拦截处理方法,promise执行,然后取出最初定义的dispatchRequest, undefined这两个元素执行请求,最后依次取出chain数组中成对的返回拦截器。

它的流程可以归纳为

  1. resolve(request interceptor fulfilled N),reject(request interceptor rejected N)

  2. -> ...

  3. -> resolve(request interceptor fulfilled 0),reject(request interceptor rejected 0)

  4. -> resolve(dispatchRequest ),reject(undefined)

  5. -> resolve(response interceptor fulfilled 0),reject(response interceptor rejected 0)

  6. -> ...

  7. -> resolve(response interceptor fulfilled N),reject(response interceptor rejected N)

转换请求返回数据,自动转换JSON数据

在axios/lib/core/dispatchRequest.js文件中暴露的核心方法dispatchRequest中,有这样两段:

  1. // 转换请求的数据  

  2. config.data = transformData(

  3. config.data,

  4. config.headers,

  5. config.transformRequest

  6. );

  7. // 转换返回的数据

  8. response.data = transformData(

  9.  response.data,

  10.  response.headers,

  11.  config.transformResponse

  12. );

axios通过设置transformResponse,可自动转换请求返回的json数据

  1. transformResponse: [function transformResponse(data) {

  2.    /*eslint no-param-reassign:0*/

  3.    if (typeof data === 'string') {

  4.        try {

  5.        data = JSON.parse(data);

  6.        } catch (e) { /* Ignore */ }

  7.    }

  8.    return data;

  9. }],

取消请求

文档上给了两种示例:

第一种:

  1. var CancelToken = axios.CancelToken;

  2. var source = CancelToken.source();

  3. axios.get('/user/12345', {

  4.  cancelToken: source.token

  5. }).catch(function(thrown) {

  6.  if (axios.isCancel(thrown)) {

  7. console.log('Request canceled', thrown.message);

  8.  } else {

  9. // 处理错误

  10.  }

  11. });

  12. // 取消请求(message 参数是可选的)

  13. source.cancel('取消请求');

第二种:

  1. var CancelToken = axios.CancelToken;

  2. var cancel;

  3. axios.get('/user/12345', {

  4.  cancelToken: new CancelToken(function executor(c) {

  5.    // executor 函数接收一个 cancel 函数作为参数

  6.       cancel = c;

  7.  })

  8. });

  9. // 取消请求

  10. cancel();

这两种方法都可以取消发出的请求。先看具体的流程:

  1. 执行 cancel 方法 -> CancelToken.promise获取到resolve -> request.abort(); -> reject(cancel);

下面来看源码是如何实现的。

在xhr.js或http.js中都有这样一段,下面取自/lib/adapters/xhr.js

  1. if (config.cancelToken) {

  2.  // 处理取消请求的方法

  3.  config.cancelToken.promise.then(function onCanceled(cancel) {

  4.    if (!request) {

  5.      return;

  6.    }

  7.    request.abort();

  8.    reject(cancel);

  9.    // 清空请求

  10.    request = null;

  11.  });

  12. }

在请求时如果设置了cancelToken参数,就会监听来自cancelToken的promise,一旦来自cancelToken的promise被触发,就会执行取消请求的流程。

cancelToken的具体实现为:

  1. /**

  2. * 可以进行取消请求操作的对象

  3. *

  4. * @param {Function} executor 具体的执行方法.

  5. */

  6. function CancelToken(executor) {

  7.  //判断executor是一个可执行的函数

  8.  if (typeof executor !== 'function') {

  9.    throw new TypeError('executor must be a function.');

  10.  }

  11.  //定义一个promise的resolve回调

  12.  var resolvePromise;

  13.  this.promise = new Promise(function promiseExecutor(resolve) {

  14.    resolvePromise = resolve;

  15.  });

  16.  var token = this;

  17.  //executor的参数为取消请求时需要执行的cancel函数

  18.  executor(function cancel(message) {

  19.    if (token.reason) {

  20.      // 已经取消了请求

  21.      return;

  22.    }

  23.    token.reason = new Cancel(message);

  24.    //触发promise的resolve

  25.    resolvePromise(token.reason);

  26.  });

  27. }

就是在上面的代码里,CancelToken会给自己添加一个promise属性,一旦cancel方法被触发就会执行取消请求的流程。

利用这个方法,一方面可以在按钮的重复点击方面大显身手,另一方面可以在数据的获取方面直接获取最新的数据。

客户端防止xsrf攻击

先来了解一下XSRF,以下内容来自维基百科。

XSRF跨站请求伪造(Cross-site request forgery),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。

举个例子:

假如一家网站执行转账的操作URL地址如下:

  1. http://www.examplebank.com/withdraw?account=AccoutName&psd=1000&for=PayeeName

那么,一个恶意攻击者可以在另一个网站上放置如下代码:

  1. <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">

如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金。

-------我是分割线,以上内容来自维基百科-------

axios是如何做的?

  1. // 添加 xsrf 请求头

  2. // 只在标准浏览器环境中才会起作用

  3. if (utils.isStandardBrowserEnv()) {

  4.  var cookies = require('./../helpers/cookies');

  5.  // 添加 xsrf 请求头

  6.  var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?

  7.      cookies.read(config.xsrfCookieName) :

  8.      undefined;

  9.  if (xsrfValue) {

  10.    requestHeaders[config.xsrfHeaderName] = xsrfValue;

  11.  }

  12. }

首先,axios会检查是否是标准的浏览器环境,然后在标准的浏览器环境中判断,如果设置了跨域请求时需要凭证且请求的域名和页面的域名相同时,读取cookie中xsrf token 的值,并设置到承载 xsrf token 的值的 HTTP 头中。

在node端支持设置代理

如果在标准浏览器环境则执行/lib/adapters/xhr.js,axios不支持proxy;如果在node环境运行,用/lib/adapters/http.js,支持proxy。

在组内的一个项目中,使用了vue的ssr服务端渲染的技术,其中ajax方案就是采用了axios。由于ssr是在服务端请求,因此在开发、测试、上线的过程中需要相应的host环境。例如在开发过程中,要么需要在程序的请求地址中写上ip地址替代域名,要么需要设置电脑的host文件,改变域名映射的ip地址。而在测试环境中,同样需要更改代码或者测试环境的host文件。这样一来,如果是改代码的方案则影响了代码的稳定性,每一次部署都需要修改代码;如果是修改host文件,则会影响环境的一致性,假如环境还部署了其他的服务,还会影响其他服务的测试。因此我们组内的男神便使用了axios的proxy功能,轻松的解决了这一问题。

  1. //判断当前的部署环境

  2. const isDev = process.env.NODE_ENV !== 'production'

  3. if(isDev){

  4.    let proxy = null;

  5.    //如果不是线上环境,且配置了代理地址则进行代理的设置,devHost是具体的ip配置

  6.    if(devHost.https){

  7.        proxy = {

  8.        host: devHost.https,

  9.        port: 443

  10.        };

  11.        Axios.defaults.proxy = proxy;

  12.    }else if(devHost.http){

  13.        proxy = {

  14.            host: devHost.http,

  15.            port: 80

  16.        };

  17.        Axios.defaults.proxy = proxy;

  18.    }else {

  19.        //do nothing

  20.    }

  21. }

而在axios的源码/lib/adapters/http.js中,则是如此实现代理的:

  1. //如果设置了代理

  2. if (proxy) {

  3.  //取代理的域名为请求的域名

  4.  options.hostname = proxy.host;

  5.  options.host = proxy.host;

  6.  options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');

  7.  options.port = proxy.port;

  8.  options.path = protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path;

  9.  // Basic proxy authorization

  10.  if (proxy.auth) {

  11.    var base64 = new Buffer(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');

  12.    options.headers['Proxy-Authorization'] = 'Basic ' + base64;

  13.  }

  14. }

内部一些针对具体项目环境的二次封装

上面基于源码具体分析了axios的各项特性,下面再来讲一讲我们在具体使用时的一些二次封装。由于axios使用get方式设置参数时,都需要使用params的方式,例如:

  1. axios.get('/user', {

  2.    params: {

  3.      ID: 12345

  4.    }

  5.  })

  6.  .then(function (response) {

  7.    console.log(response);

  8.  })

  9.  .catch(function (error) {

  10.    console.log(error);

  11.  });

而之前使用vue-resource则习惯直接写上参数,形如:

  1. axios.get('/user',

  2.    {

  3.      ID: 12345

  4.    }

  5.  )

  6.  .then(function (response) {

  7.    console.log(response);

  8.  })

  9.  .catch(function (error) {

  10.    console.log(error);

  11.  });

因此,对于组内的axios统一加了一层封装,承接之前的使用习惯:

  1. let get = Axios.get;

  2. /**

  3. * 对原方法的get做一层装饰,可以传参时不必写params参数,直接传递参数对象,同时对已有的params写法兼容

  4. * @param args 参数 url config

  5. * @returns {*}

  6. */

  7. Axios.get = (...args) => {

  8.    let param = args[1];

  9.    //如果以参数方式传递query,同时不存在axios需要的key:params,则为它添加

  10.    if (param && !param.hasOwnProperty('params')) {

  11.        args[1] = {

  12.            params: param

  13.        }

  14.    }

  15.    return get(...args)

  16. }

这里需要注意的是,要确保在提交到服务端的query参数中不包含‘params’字段,不然还是要使用默认的参数格式。

而对于post方式,则做了如下封装:

  1. let post = Axios.post;

  2. /**

  3. * axios的post请求默认会依据数据类型设置请求头,但是目前后台没有识别json,因此统一将请求的数据设置为x-www-form-urlencoded需要的字符串格式

  4. * @param args 参数 url data config

  5. * @returns {*}

  6. */

  7. Axios.post = (...args) => {

  8.    let data = args[1];

  9.    //判断是对象就转化为字符串

  10.    if (data && typeof data === 'object') {

  11.        args[1] = qs.stringify(data)

  12.    }

  13.    return post(...args)

  14. }

axios使用方便,功能齐备强大,其中的一些编程思想也很不入俗套,是一款前后端通用的ajax请求框架,目前在github上已经有接近36K的赞,其优秀程度可见一斑。本文通过他的一些特性,分析了部分源码,旨在能够在使用它的同时,更加懂得它。

——————————————————

长按二维码,关注大转转FE

axios的秘密的更多相关文章

  1. TypeScript: Angular 2 的秘密武器(译)

    本文整理自Dan Wahlin在ng-conf上的talk.原视频地址: https://www.youtube.com/watch?v=e3djIqAGqZo 开场白 开场白主要分为三部分: 感谢了 ...

  2. [C#] string 与 String,大 S 与小 S 之间没有什么不可言说的秘密

    string 与 String,大 S 与小 S 之间没有什么不可言说的秘密 目录 小写 string 与大写 String 声明与初始化 string string 的不可变性 正则 string ...

  3. 网站的SEO以及它和站长工具的之间秘密

    博客迁移没有注意 URL 地址的变化,导致百度和 google 这两只爬虫引擎短时间内找不到路.近段时间研究了下国内最大搜索引擎百度和国际最大搜索引擎google的站长工具,说下感受. 百度的站长工具 ...

  4. 《WePayUI组件设计的秘密》——2016年第一届前端体验大会分享

    本文是博主参加第一届前端体验大会 | 物勒工名做的分享<WePayUI组件设计的秘密>,内容主要分为2个部分: 一.浅析UI库/框架的未来 讨论的UI库或者框架,主要包含展示和交互的css ...

  5. [从产品角度学EXCEL 03]-单元格的秘密

    这是<从产品角度学EXCEL>系列——单元格的秘密. 前言请看: 0 为什么要关注EXCEL的本质 1 EXCEL是怎样运作的 2 EXCEL里的树形结构 或者你可以去微信公众号@尾巴说数 ...

  6. 为什么axios请求接口会发起两次请求

    之前在使用axios发现每次调用接口都会有两个请求,第一个请求时option请求,而且看不到请求参数,当时也没注意,只当做是做了一次预请求,判断接口是否通畅,但是最近发现并不是那么回事. 首先我们知道 ...

  7. axios基本用法

    vue更新到2.0之后,作者就宣告不再对vue-resource更新,而是推荐的axios,前一段时间用了一下,现在说一下它的基本用法. 首先就是引入axios,如果你使用es6,只需要安装axios ...

  8. 2016第16本:TED演讲的秘密

    花0.01元抢购了<得到APP>中的<成甲说书:TED演讲的秘密>,不到30分钟的音频,感觉全是干货,基本不用看原书了.如果在以后的演讲中随便应用几条都可以让演讲水平提升一大截 ...

  9. 字符串的replace()方法隐藏着什么不可告人秘密?

    最近在做JS算法项目时发现一个令我匪夷所思的问题, 这里想记录一下问题. 首先介绍一下字符串replace()方法的基本用法. replace() 方法使用一个替换值(replacement)替换掉一 ...

随机推荐

  1. .NET core RSA帮助类

    解决 Operation is not supported on this platform 异常 直接上代码: public class RSAHelper { /// <summary> ...

  2. instanceof简单用法

    语法: 对象 instanceof 类: 含义:如果这个对象时这个类或者这个类的子类的实例化,那么结果及时ture, 否则 false. 常常用来判断一个类是否是某个类的子类,以此判断A类是否继承或者 ...

  3. PNP的学习-P3P

    PNP方法是为了解决在当前两帧图像中,已知前一帧图像上的3dLandmark点和当前帧的2d特征点,求取当前帧的pose. PNP主要有P3P.EPNP.UPNP.DLT.MRE(LS Iterati ...

  4. 动态库的链接和链接选项-L,-rpath-link,-rpath

    https://my.oschina.net/shelllife/blog/115958 链接动态库 如何程序在连接时使用了共享库,就必须在运行的时候能够找到共享库的位置.linux的可执行程序在执行 ...

  5. Codeforces 1120 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 传送门 A题 传送门 题意简述:给你一个mmm个数的数列,现在规定把一个数列的1,2,...,k1,2,...,k1,2,...,k分成第一组,把k+1, ...

  6. \usepackage{ulem}带下划线的问题解决

    其实正文应该是斜体字的,但是有可能默认模板会导致斜体变下划线的问题,解决方法如下: \usepackage{ulem} 在 \documentclass[format=acmsmall, review ...

  7. rem 是如何实现自适应布局的

    摘要:rem是相对于根元素<html>,这样就意味着,我们只需要在根元素确定一个px字号,则可以来算出元素的宽高.本文讲的是如何使用rem实现自适应.· rem这是个低调的css单位,近一 ...

  8. python3 django1.11 安装xadmin 的方法,亲测可用

    首先需要Pip安装如下的包ip install django-crispy-forms pip install django-import-export pip install django-reve ...

  9. Maths | 病态问题和条件数

    目录 1. 概念定义 1.1. 病态/ 良态问题 1.2. 适定/ 非适定问题 1.3. 良态/ 病态矩阵和条件数 2. 病态的根源 3. 计算条件数的方法 3.1. 与特征值的关系 3.2. 与奇异 ...

  10. 所有子节点、Procedure、MySQL

    在Oracle 中我们知道有一个 Hierarchical Queries 通过CONNECT BY 我们可以方便的查了所有当前节点下的所有子节点.但很遗憾,在MySQL的目前版本中还没有对应的功能. ...