阅读 383

Axios or fetch():你更中意哪一个?

作者:Faraz Kelhini
译者:Da
原网址:blog.logrocket.com/axios-or-fe…

在我刚发布的如何像专业人士一样使用axios发出高大上的HTTP请求一文中,描述了使用Axios库的各种好处。但是,要知道Axios并不总是最好的解决方案,在有些情况下我们有比Axios更好的选择。

毫无疑问,一些开发者们相对于内置的API更青睐于axios,因为简单,好用,但是很多人都高估了对这样一个库的需求。fetch()这一内置API就能完美重现Axios的主要功能,而且它还有在所有主流浏览器中轻松使用的附加优势哟!

在这篇文章汇总,我们会对比fetch()和Axios,看他们在不同的场景下有怎样的使用方法。希望在读完这篇文章后,你能对这两者都有更多的了解。

基础语法

在深入了解Axios的高级功能之前,我们先来就基础语法与fetch()进行一个比较。下面的代码是我们使用Axios向一个URL发送自定义请求头的POST请求,因为Axios会自动将数据转换成JSON格式,我们就不用手动转换了。

// axios
const options = {
  url: 'http://localhost/test.htm',
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json;charset=UTF-8'
  },
  data: {
    a: 10,
    b: 20
  }
};

axios(options)
  .then(response => {
    console.log(response.status);
  });
复制代码

现在我们使用fetch()重写上面的功能,然后对比一下

// fetch()
const url = 'http://localhost/test.htm';
const options = {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json;charset=UTF-8'
  },
  body: JSON.stringify({
    a: 10,
    b: 20
  })
};

fetch(url, options)
  .then(response => {
    console.log(response.status);
  });
复制代码

注意:

  • 发送数据,fetch()使用的是body属性,而Axios使用的是data属性;
  • fetch()方法里的data是被字符串化的;
  • 在fetch()中,URL是作为参数被使用,而在Axios中,URL是作为options的一个配置项。

向后兼容

Axios的一个主要卖点就是它兼容范围非常广,即使是在老牌浏览器中像IE11,都能不需要issue直接运行。但是fetch()就比较尴尬了,它只能支持Chrome 42+, Firefox 39+, Edge 14+, 和 Safari 10.1+ (你可以在这里看到详细的兼容表)

如果向后兼容是你决定使用Axios的唯一理由,那大可不必专门装个HTTP库,你可以像这样使用polyfill(点这里)在不支持fetch()的web浏览器上实现类似的功能。注意,在使用fetch polyfill之前,你需要先通过npm命令安装它

npm install whatwg-fetch --save
复制代码

然后你就可以像这样使用fetch()啦

import 'whatwg-fetch'
window.fetch(...)
复制代码

友情提示,在低版本的浏览器中,你可能同样需要装一个promise的polyfill

响应超时

开发者们更倾向于Axios的另一个原因就是:简单快捷的配置响应超时。在Axios中,你可以使用在config对象中的timeout属性设置终止请求的超时时长(毫秒),如下:

axios({
  method: 'post',
  url: '/login',
  timeout: 4000,    // 4 seconds timeout
  data: {
    firstName: 'David',
    lastName: 'Pollock'
  }
})
.then(response => {/* handle the response */})
.catch(error => console.error('timeout exceeded'))
复制代码

fetch()通过AbortController实例也可以实现类似的功能,不过就是不像Axios那么方便。。。

const controller = new AbortController();
const options = {
  method: 'POST',
  signal: controller.signal,
  body: JSON.stringify({
    firstName: 'David',
    lastName: 'Pollock'
  })
};  
const promise = fetch('/login', options);
const timeoutId = setTimeout(() => controller.abort(), 4000);

promise
  .then(response => {/* handle the response */})
  .catch(error => console.error('timeout exceeded'));
复制代码

上文中我们通过AbortController()构造函数创建了一个AbortController对象,它的功能就是让我们可以实现延时终止请求。signal 是AbortController的一个只读属性,提供了跟请求交流或终止请求的途径。如果这个服务没有在4秒钟内响应,那么controller.abort()就会被调用,请求将会被终止。

自动转换JSON数据

我们刚刚说过,当我们发送请求时,Axios可以自动字符串化数据(你也可以覆盖默认行为,自己定义不同的转换机制)。但是当我们使用fetch()时,就不得不手动实现转换了,对比如下:

// axios
axios.get('https://api.github.com/orgs/axios')
  .then(response => {
    console.log(response.data);
  }, error => {
    console.log(error);
  });

// fetch()
fetch('https://api.github.com/orgs/axios')
  .then(response => response.json())    // 多出来一步
  .then(data => {
    console.log(data) 
  })
  .catch(error => console.error(error));
复制代码

能自动化转换数据格式当然很nice,但记住,这些事情用fetch()也可以做到。

HTTP拦截器

Axios的另一个主要特点是:它具备拦截HTTP请求的功能。在应用程序向后台服务(或者反过来的情况,像e.g,日志打印,认证方式)发送请求前,你需要检查或修改HTTP请求时,HTTP拦截器就派上用场了。用了拦截器,你就不需要为每个HTTP请求都写上相同的代码了。

以下代码教你在Axios中如何声明请求拦截器

axios.interceptors.request.use(config => {
  // log a message before any HTTP request is sent
  console.log('Request was sent');

  return config;
});

// sent a GET request
axios.get('https://api.github.com/users/sideshowbarker')
  .then(response => {
    console.log(response.data);
  });
复制代码

在上面的代码中,axios.interceptors.request.use()就是用来定义HTTP请求发送之前运行的代码的。

在默认情况下,fetch()并不提供拦截请求的方法 ,但是找到解决方案也并非难事哦。你可以重写全局的fetch()方法,然后自己定义拦截器,就像这样:

fetch = (originalFetch => {
  return (...arguments) => {
    const result = originalFetch.apply(this, arguments);
      return result.then(console.log('Request was sent'));
  };
})(fetch);

fetch('https://api.github.com/orgs/axios')
  .then(response => response.json())
  .then(data => {
    console.log(data) 
  });
复制代码

下载进度

进度提示在下载大文件时是非常有用的,尤其是在用户网络比较差的时候。之前JavaScript的开发者们是使用XMLHttpRequest.onprogress回调实现进度提示的。fetch()API并没有onprogress的事件回调,作为替代方案,它通过响应对象的body属性提供了ReadableStream实例。

以下示例简单说明了使用ReadableStream实现图片下载的进度反馈

// original code: https://github.com/AnthumChris/fetch-progress-indicators

<div id="progress" src="">progress</div>
<img id="img">

<script>

'use strict'

const element = document.getElementById('progress');

fetch('https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg')
  .then(response => {

    if (!response.ok) {
      throw Error(response.status+' '+response.statusText)
    }

    // ensure ReadableStream is supported
    if (!response.body) {
      throw Error('ReadableStream not yet supported in this browser.')
    }

    // store the size of the entity-body, in bytes
    const contentLength = response.headers.get('content-length');

    // ensure contentLength is available
    if (!contentLength) {
      throw Error('Content-Length response header unavailable');
    }

    // parse the integer into a base-10 number
    const total = parseInt(contentLength, 10);

    let loaded = 0;

    return new Response(

      // create and return a readable stream
      new ReadableStream({
        start(controller) {
          const reader = response.body.getReader();

          read();
          function read() {
            reader.read().then(({done, value}) => {
              if (done) {
                controller.close();
                return; 
              }
              loaded += value.byteLength;
              progress({loaded, total})
              controller.enqueue(value);
              read();
            }).catch(error => {
              console.error(error);
              controller.error(error)                  
            })
          }
        }
      })
    );
  })
  .then(response => 
    // construct a blob from the data
    response.blob()
  )
  .then(data => {
    // insert the downloaded image into the page
    document.getElementById('img').src = URL.createObjectURL(data);
  })
  .catch(error => {
    console.error(error);
  })

function progress({loaded, total}) {
  element.innerHTML = Math.round(loaded/total*100)+'%';
}

</script>
复制代码

在Axios中实现进度提示很简单,尤其是配套使用 Axios Progress Bar 模块,首先,你要引入下面的样式和js

<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/nprogress.css" />

<script src="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/index.js"></script>
复制代码

然后你就能像这样使用进度条啦:

<img id="img">

<script>

loadProgressBar();

const url = 'https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg';

function downloadFile(url) {
  axios.get(url, {responseType: 'blob'})
    .then(response => {
      const reader = new window.FileReader();
      reader.readAsDataURL(response.data); 
      reader.onload = () => {
        document.getElementById('img').setAttribute('src', reader.result);
      }
    })
    .catch(error => {
      console.log(error)
    });
}

downloadFile(url);

</script>
复制代码

上面的代码使用了FileReader API异步读取下载的图片,readAsDataURL 方法返回图片的Base64格式字符串,后面会被插进img的src属性中,将图片展示出来。

同时发起多个请求

当我们需要同时发起多个请求时,Axios提供了axios.all()方法,只需要将请求数组发送给这个方法,然后使用axios.spread()将响应数组的属性分配给一个个的变量:

axios.all([
  axios.get('https://api.github.com/users/iliakan'), 
  axios.get('https://api.github.com/users/taylorotwell')
])
.then(axios.spread((obj1, obj2) => {
  // Both requests are now complete
  console.log(obj1.data.login + ' has ' + obj1.data.public_repos + ' public repos on GitHub');
  console.log(obj2.data.login + ' has ' + obj2.data.public_repos + ' public repos on GitHub');
}));

复制代码

你同样可以通过内置的Promise.all()方法实现上述功能,将所有请求作为数组传给promise.all(),然后使用async 处理这些响应,如下:

Promise.all([
  fetch('https://api.github.com/users/iliakan'),
  fetch('https://api.github.com/users/taylorotwell')
])
.then(async([res1, res2]) => {
  const a = await res1.json();
  const b = await res2.json();
  console.log(a.login + ' has ' + a.public_repos + ' public repos on GitHub');
  console.log(b.login + ' has ' + b.public_repos + ' public repos on GitHub');
})
.catch(error => {
  console.log(error);
});
复制代码

总结

Axios通过紧凑的代码包给我们提供了很多易于使用的API,满足我们大部分的HTTP通讯场景。但是,如果你更愿意坚持使用原生API,没有什么事情可以阻挡你的步伐!你可以自己实现Axios那些特性哦!

如本文所述,使用web浏览器提供的fetch()方法可以完美重写Axios库提供的那些主要功能。最终,是否需要一个HTTP库完全取决于你使用内置API是否感觉舒适。