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. 使用svn进行文件和文件夹的忽略

    使用svn进行文件和文件夹的忽略 黑泥卡 关注  0.3 2016.08.16 22:42* 字数 786 阅读 20554评论 7喜欢 15 如何使用svn忽略文件和文件夹如果你之前尝试过git,你 ...

  2. get windows auth code

    public static WindowsIdentityInfo GetWindowsIdentityInfo(HttpContext context) { WindowsIdentityInfo ...

  3. thinkphp 5 使用oss

    简单的tp5中上传到 图片到oss我本地开发环境为:WAMP;php版本:5.6.19TP版本:5.1.13 1.使用composer 安装 composer require aliyuncs/oss ...

  4. 常用API String

    Java的API以及Object类 Java的API Java的API(API: Application(应用) Programming(程序) Interface(接口)) Java API就是JD ...

  5. JPA的初级CRUD-01

    一.JPA 1.1 什么是JPA JPA:(Java Persistence API) ORM的规范 JPA是规范,Hibernate是它的实现(不唯一,但最好) 最底层的操作还是JDBC(引入驱动包 ...

  6. Notepad++编写运行python程序

    Notepad++编写运行python程序. 1.菜单栏->语言->P->Python设置语言为Python 2.写好代码后ctrl+s保存文件为py文件 3.菜单栏->运行, ...

  7. idea使用自动生成变量的时候总是默认final,每次都会跳出来declare final的选项,并且默认是勾选的,很难受

    看下截图: 我这边首先要保证:settings----->Editor------>Code Style-------->java下的这两个选项不被勾选; 然后在生成变量的时候,再次 ...

  8. LCD调试

    (1) 液晶显示模式 并行:MCU接口.RGB接口.Vysnc接口 串行:SPI接口.MDDI接口 (2) 屏幕颜色 实质上即为色阶的概念.色阶是表示手机液晶显示屏亮度强弱的指数标准,也就是通常所说的 ...

  9. PowerShell工作流学习-5-自定义活动

    关键点: a)除了内置活动和自定义活动,还可以用C# 编写自定义活动,并将其包括在 XAML 工作流和脚本工作流中,若要将自定义活动添加到脚本工作流中,请使用 #Requires 语句的 Assemb ...

  10. [f]聊天的时间格式化

    代码如下: 参数: t: 时间戳, type:返回格式(1:IM界面,其他:会话列表) function formartTime(t, type) { var oldtime = new Date(t ...