Fetch和Axios

一些基本认识

  1. 通过浏览器访问Web,一般时候就是通过Http协议请求一个页面
  2. Ajax不是一种技术,而是一个术语,描述了利用现有技术的方法。技术上包括HTML、CSS、JS、DOM、XML等,以及其核心:window.XMLHttpRequest这个对象——或者我们也可以说是Api。利用Ajax这个结合了各种技术的方法,区别于form表单,在不刷新页面的前提下,通过Http请求更新网页的数据
  3. 在jQuery时代,$.ajax是对这个Api的一个封装,提高了兼容性和易用性
  4. fetch是新一代的Api,也就是说,它的地位和传统的Ajax是对等的,我们说的是window.fetch,在客户端是基于的Promise的JavaScript接口。
  5. axios也是基于Promise的,看了源码之后,发现它和$.ajax一样,其实是对XMLHttpRequest的封装,但是又不止于此,它还支持Node.js环境的http请求——当然具体的Api不一样,但是好处是两边通用。
  6. RESTful:一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。

Fetch

从XHR到Fetch

  1. XMLHttpRequest 是一个非常粗糙的api,不符合关注分离(Separation of Concerns)的原则——说的详细点,就是“输入、输出和用事件来跟踪的状态混杂在一个对象里”,配置和调用方式非常混乱,前端不仅要做各个浏览器的兼容性,还饱受回调地狱的折磨。其基于事件的模型与最近JavaScript流行的Promise以及基于生成器的异步编程模型不太搭(事件模型在处理异步上有点过时了)。
  2. jQuery的AJAX很好的封装了原生AJAX的代码,在兼容性和易用性方面都做了很大的提高,而且jQuery还把JSONP方法封装在了AJAX里面,可以方便的跨域。
  3. Fetch基于 Promise,旧浏览器不支持,需要pollyfill ES6-promise。
  4. 其调用返回一个Promise,这个Promise会在请求响应后被Resolve,并传回Response对象。 当遇到网络错误时,Fetch返回的Promise会被Reject,并传回TypeError。

Fetch的使用

  1. 使用Fetch大概是下面的样子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* url (必须), options (可选) */
    fetch('/some/url', {
    method: 'get'
    }).then(function(response) {

    }).then(function(returnedValue) {
    /* 还可以执行其他的,链式处理,将异步变为类似单线程的写法: 高级用法. */
    }).catch(function(err) {
    /* 出错了;等价于 then 的第二个参数,但这样更好用更直观 */
    });

Fetch的Api

  1. Headers允许对HTTP request和response headers执行各种操作。 这些操作包括:检索, 设置, 添加和删除。var headers = new Headers()然后有一系列的Api可以去操作,当然也可以在初始化的时候直接传入对象参数
  2. Request表示一次调用的请求信息。可以使用构造函数Request.Request()来创建一个新的Request对象。
    1. mode属性用来决定是否允许跨域请求,以及哪些response属性可读。可选的mode属性值为same-originno-cors(默认)以及cors
    2. credentials枚举属性决定了Cookies是否能跨域得到。
  3. Response呈现了对一次请求的响应数据。可以使用Response.Response()构造函数来创建一个Response对象,但一般更可能遇到的情况是,其他的API操作返回了一个Response对象:Response实例通常在fetch()的回调中获得。
  4. 无论Request还是Response都可能带着body,Request和Response都为他们的body提供了一些方法,这些方法都读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次),并且都返回一个Promise对象。(区别在于格式不同)

Fetch的一些缺陷

  1. 使用 Fetch 无法取消一个请求。这是因为 Fetch API 基于 Promise,而 Promise 无法做到这一点。
  2. Fetch在服务器返回4xx、5xx时是不会抛出错误的——不会走Reject,而是Resolve,所以需要增加判断
  3. 不支持JSONP,但可以引入fetch-jsonp插件
  4. 没有interceptor(拦截器)的Api,所以一般需要自己封装一层
  5. 没有获取状态的方法:isRejected,isResolved,标准 Promise 没有提供获取当前状态 rejected 或者 resolved 的方法。只允许外部传入成功或失败后的回调。
  6. 不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
  7. always 可以通过在 then 和 catch 里重复调用方法实现。finally 也类似。progress 这种进度通知的功能还没有用过,暂不知道如何替代。
  8. Fetch没有办法原生监测请求的进度,而XHR可以

一些Fetch的库

  1. isomorphic-fetch:支持NodeJS
  2. whatwg-fetch:一个封装,和babel-pollyfill一样。如果自身支持fetch,直接返回,用自身的。
  3. Fetch 也可以做现在流行的同构应用,有人基于 Fetch 的语法,在 Node 端基于 http 库实现了 node-fetch,又有人封装了用于同构应用的 isomorphic-fetch。(同构(isomorphic/universal)就是使前后端运行同一套代码的意思,后端一般是指 NodeJS 环境。)
  4. 支持:
    1. 由于 IE8 是 ES3,需要引入 ES5 的 polyfill: ES5-shim, ES5-sham
    2. 引入 Promise 的 polyfill: ES6-promise
    3. 引入 Fetch 探测库:fetch-detector
    4. 引入 Fetch 的 polyfill: fetch-ie8
    5. 可选:如果你还需要使用JSONP,则引入 fetch-jsonp
    6. 可选:开启 Babel 的 runtime 模式,使用 async/await 语法糖
  5. 写法:

    1. then catch的写法还是有一点怪,但是我们还有es7的async/await
    2. 比如这样,写异步代码就像写同步代码一样爽,await 后面可以跟 Promise 对象,表示等待 Promise resolve() 才会继续向下执行,如果 Promise 被 reject() 或抛出异常则会被外面的 try…catch 捕获。

      1
      2
      3
      4
      5
      6
      7
      8
      try {
      let response = await fetch(url);
      let data = response.json();
      console.log(data);
      } catch(e) {
      console.log("Oops, error", e);
      }
      // 注:这段代码如果想运行,外面需要包一个 async function
  6. Promise,generator/yield,await/async 都是现在和未来 JS 解决异步的标准做法,可以完美搭配使用。这也是使用标准 Promise 一大好处。把第三方 Promise 库的代码全部转成标准 Promise,为以后全面使用 async/await 做准备,是非常明智的

Axios

什么是Axios

  1. Axios是对原生XHR的封装(在这一点上和$.ajax一样)的HTTP库
  2. 它有以下特点
    • 利用XMLHttpRequest对象创建请求
    • 可以在Node.js环境创建Http请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求数据和响应数据
    • 取消请求
    • 自动转换 JSON 数据
    • 客户端支持防御 XSRF(让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。)
    • 提供了一些并发请求的接口(重要,方便了很多的操作,后续操作在他们都完成的时候才执行。)
    • jQuery可以用JSONP实现跨域请求,但是Axios的作者明确表态不支持JSONP,Axios需要服务端配合设置Access-Control-Allow-Origin

Axios的例子

  1. GET请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    axios.get('/user', {
    params: {
    ID: 12345
    }
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    });
  2. POST请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    });
  3. 发起并发请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function getUserAccount() {
    return axios.get('/user/12345');
    }

    function getUserPermissions() {
    return axios.get('/user/12345/permissions');
    }

    axios.all([getUserAccount(), getUserPermissions()])
    .then(axios.spread(function (acct, perms) {
    /* 两个请求现在都执行完成 */
    }));
  4. 传参的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    axios({
    method: 'post',
    url: '/user/12345',
    data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
    }
    });
    axios('/user/12345');
    /* 别名,还有reuqest、get、delete、head、put、patch */
    axios.post(url[, data[, config]])
  5. 请求超时,重新请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    /* 在main.js设置全局的请求次数,请求的间隙 */
    axios.defaults.retry = 4;
    axios.defaults.retryDelay = 1000;

    axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
    var config = err.config;
    /* If config does not exist or the retry option is not set, reject */
    if(!config || !config.retry) return Promise.reject(err);

    /* Set the variable for keeping track of the retry count */
    config.__retryCount = config.__retryCount || 0;

    /* Check if we've maxed out the total number of retries */
    if(config.__retryCount >= config.retry) {
    /* Reject with the error */
    return Promise.reject(err);
    }

    /* Increase the retry count */
    config.__retryCount += 1;

    /* Create new promise to handle exponential backoff */
    var backoff = new Promise(function(resolve) {
    setTimeout(function() {
    resolve();
    }, config.retryDelay || 1);
    });

    /* Return the promise in which recalls axios to retry the request */
    return backoff.then(function() {
    return axios(config);
    });
    });