<devzera

Aprendendo e compartilhando tecnologia

Axios ou fetch()

Qual você deveria usar?

Esse artigo é uma tradução livre de Axios or fetch(): Which should you use?por Faraz Kelhini.


Em minha recente postagem “Como fazer requests como um profissional usando Axios,” eu discuti os benefícios de usar essa biblioteca. Mesmo assim, é importante saber que nem sempre o Axios é a melhor solução, existindo melhores opções para fazer requisições(requests) HTTP.

Sem questionar ou sequer titubear, alguns desenvolvedores preferem o Axios ao invés de APIs internas. Mas em sua maioria, superestimam a necessidade do uso dessa biblioteca. A API fetch() é perfeitamente capaz de reproduzir as principais funcionalidades do Axios, e tem a vantagem de estar presentes em todos os navegadores modernos.

Nesse artigo, será feito o comparativo entre usar o fetch() e Axios, vendo como podem ser usados diferentes usos. Espero que, ao final do artigo, você possa ter uma compreensão melhor de ambas as APIs.

Syntax básica

Antes de nos aprofundar em funcionalidades avançadas do Axios, vamos comparar sua syntax básica com o que é usado no fetch(). O exemplo a seguir mostra como fazer uma request POST usando Axios com cabeçalhos customizados(custom headers). O Axios converte automaticamente a resposta para JSON, para que você não precise fazer isso.

// 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);
  });

Agora compare esse código com o que usaremos no fetch(), que produzirá o mesmo resultado:

// 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);
  });

Perceba que:

  • Para enviar dados, o fetch() usa a propriedade “body”, enquanto o Axios faz uso da propriedade “data”.
  • Os dados enviados pelo fetch() devem estar stringuificados(stringified)
  • A URL é passada como argumento no fetch(). No Axios, entretanto, a URL faz parte do conjunto de valores enviados no objeto “options”.

Compatibilidade com versões anteriores

Um dos grandes pontos de aderência ao Axios é seu suporte aos navegadores. Mesmo os antigos como IE11 pode rodá-lo sem problemas. O fetch(), por outro lado, apenas é suportado pelo Chrome 42+, Firefox 39+, Edge 14+, e Safari 10.1+ (você pode ver a tabela completa de suporte no site — Can I use).

Se a sua justificativa para usar o Axios for apenas sua compatabilidade, você realmente não precisa de uma biblioteca HTTP. Ao invés disso, use o fetch() com um polyfill como esse para implementar uma funcionalidade similar em navegadores que não suportam seu uso. Para fazer uso desse polyfill, faça sua instalação via npm através do comando:

npm install whatwg-fetch --save

Aí pra fazer uma request é só fazer assim:

import 'whatwg-fetch'
window.fetch(...)

Tenha em mente que você talvez precisará um polyfill para promise nesses navegadores antigos.

Timeout de resposta(Response timeout)

A forma simples de colocar um timeout no Axios é uma das muitas razões dos desenvolvedores usá-lo ao invés do fetch(). No Axios, você pode usar a opção timeout no objeto de configuração para informar o tempo em milissegundos antes da requisição ser abortada. Exemplo:

axios({
  method: 'post',
  url: '/login',
  timeout: 4000, // 4 segundos timeout
  data: {
    firstName: 'David',
    lastName: 'Pollock'
  }
})
.then(response => {/* tratamento da resposta */})
.catch(error => console.error('timeout excedido'))

Fetch() provê uma funcionalidade similar através da interface AbortController. Não é simples como no Axios, mas é possível:

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 => {/* tratamento da resposta */})
  .catch(error => console.error('timeout excedido'));

Aqui nós criamos o AbortController, que nos permitirá abortar a requisição. A propriedade signal é apenas de leitura(read-only), provendo meios de se comunicar com uma request ou abortá-la. Se o servidor não responder nada em até 4 segundos, o controller.abort() será chamado, encerrando a operação.

Conversão em JSON automática

Como foi dito anteriormente, o Axios stringuifica(stringifies) automaticamente os dados enviados nas requests(você também pode sobrescrever o compartamento padrão e criar um meio de transformar o dado). Quando usamos fetch() você precisa fazer isso manualmente. Veja:

// 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())    // passo extra
  .then(data => {
    console.log(data)
  })
  .catch(error => console.error(error));

Essa conversão dos dados é uma funcionalidade bacana, mas novamente, não é nada que o fetch() não consiga fazer.

Interceptores HTTP(HTTP interceptors)

Uma das principais funcionalidades do Axios é a habilidade de interceptar requisições(requests) HTTP. Esses interceptadores são úteis quando é preciso examinar ou alterar requisições HTTP, sendo ela da sua aplicação para o servidor e vice-versa(ex: log, autenticação, etc). Fazendo uso de um interceptador você não precisará escrever um código separado para cada requisição HTTP.

Segue abaixo como declarar um interceptador usando o Axios:

axios.interceptors.request.use(config => {
  // loga(log) uma mensagem antes da requisição HTTP ser enviada
  console.log('A requisição foi enviada');
  return config;
});
// faz uma requisição GET
axios.get('https://api.github.com/users/sideshowbarker')
  .then(response => {
    console.log(response.data);
  });

No código acima, o método axios.interceptors.request.use() é usado para definir um código que será executado antes que a requisição HTTP seja enviada.

O fetch() não possui nada desse tipo para fazermos essa interceptação, mas não é difícil de contarmos isso, para obtermos o mesmo efeito. Você pode sobrescrever o método global fetch, e definir seu próprio interceptador, como mostrado abaixo:

fetch = (originalFetch => {
  return (...arguments) => {
    const result = originalFetch.apply(this, arguments);
      return result.then(console.log('A requisição foi enviada'));
  };
})(fetch);
fetch('https://api.github.com/orgs/axios')
  .then(response => response.json())
  .then(data => {
    console.log(data)
  });

Progresso de download

Indicadores de progressão são bem úteis quando carregamos muitos assets, principalmente quando o usário não tem uma internet bacana. Anteriormente, os programadores Javascript faziam uso do callback XMLHttpRequest.onprogress para implementar esses indicadores de progresso.

A API do Fetch não tem uma método onprogress para tratar esse caso. Ao invés disso, provê uma instância de ReadableStream através do body no objeto da resposta.

O exemplo seguinte demonstra o uso do ReadableStream para prover aos usuários um feedback imediato durando o download de uma imagem:

// código original: 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)
    }
    // tenha certeza que o ReadableStream é suportado
    if (!response.body) {
      throw Error('ReadableStream not yet supported in this browser.')
    }
    // armazene o tamanho da entity-body em bytes
    const contentLength = response.headers.get('content-length');
    // garanta que o contentLength está disponível
    if (!contentLength) {
      throw Error('Content-Length response header unavailable');
    }
    // converta o integer em um número de base 10
    const total = parseInt(contentLength, 10);
    let loaded = 0;
    return new Response(
      // cria e retorna um 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 =>
    // construa um blob a partir da data
    response.blob()
  )
  .then(data => {
    // insira a imagem carregada na página
    document.getElementById('img').src = URL.createObjectURL(data);
  })
  .catch(error => {
    console.error(error);
  })
function progress({loaded, total}) {
  element.innerHTML = Math.round(loaded/total*100)+'%';
}
</script>

Implementar uma barra de progresso, ou algo similar usando o Axios é mais simples, especialmente se você usar o Axios Progress Bar. Antes de mais nada, você precisa incluir esses carinhas daqui de baixo:

<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>

Aí você pode começar a implementar a barra de progresso, como visto abaixo:

<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>

Esse código faz uso da API FileReader, que de forma assíncrona lerá o arquivo a imagem baixada. O método readAsDataURL retorna a imagem com o encode Base64, que será inserida no atributo src da tag img para exibi-lá.

Requests simultâneas

To make multiple simultaneous requests, Axios provides the axios.all() method. Simply pass an array of requests to this method, then use axios.spread() to assign the properties of the response array to separate variables:

axios.all([
  axios.get('https://api.github.com/users/iliakan'),
  axios.get('https://api.github.com/users/taylorotwell')
])
.then(axios.spread((obj1, obj2) => {
  // Ambas requests foram finalizadas
  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');
}));

Você consegue o mesmo resultado fazendo uso do método Promise.all(). Para isso, é só você passar suas requests dentro de um array pra Promise.all(), e em seguida, lide com a resposta com uma funçao assíncrona, como o exemplo abaixo:

Promise.all([
  fetch('https://api.github.com/users/iliakan'),
  fetch('https://api.github.com/users/taylorotwell')
])
.then(async([res1, res2]) => { //async function
  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);
});

Conclusão

O Axios fornece uma API fácil de usar em um pacote compacto para a maioria das suas necessidades de comunicação HTTP.No entanto, se você preferir ficar com APIs nativas, nada o impede de implementar os recursos do Axios.

O Axios fornece uma API bem fácil de usar através de um pacote compacto que você poderá usar para a maioria das suas necessidades de comunicação HTTP. Entretanto, se você preferir lidar com APIs nativas também será possível.

Conforme discutido neste artigo, é perfeitamente possível reproduzir os principais recursos da biblioteca Axios usando o método fetch () fornecido pelos navegadores da web. Por fim, se vale a pena carregar uma API HTTP do cliente, depende se você se sente à vontade trabalhando com APIs internas.

Conforme discutido neste artigo, é perfeitamente possível reproduzir os principais recursos do Axios através do método fetch(). Para finalizar, se vale ou não você fazer uso de uma lib para fazer requisições HTTP, depende unicamente de você, e se está à vontade trabalhando com APIs internas.

Comentários