Introdução a fetch()

A API fetch() está entrando no objeto da janela e procurando substituir os XHRs.

XMLHttpRequest longo

fetch() permite que você faça solicitações de rede semelhantes a XMLHttpRequest (XHR). A principal diferença é que a API Fetch usa promessas, o que permite uma API mais simples e limpa, evitando o inferno de callbacks e a necessidade de se lembrar da API complexa de XMLHttpRequest.

Compatibilidade com navegadores

  • 42
  • 14
  • 39
  • 10.1

Origem

A API Fetch está disponível no escopo global do Service Worker desde o Chrome 40, mas será ativada no escopo da janela no Chrome 42. Há também um polyfill do GitHub para busca que pode ser usado hoje.

Caso você nunca tenha usado promessas em JavaScript, confira a Introdução às promessas do JavaScript.

Solicitação básica de busca

Vamos começar comparando um exemplo simples implementado com XMLHttpRequest e depois com fetch. Queremos apenas solicitar um URL, receber uma resposta e analisá-lo como JSON.

XMLHttpRequest

Um XMLHttpRequest precisaria que dois listeners fossem definidos para processar os casos de sucesso e erro e uma chamada para open() e send(). Exemplo de documentos do MDN.

function reqListener() {
    var data = JSON.parse(this.responseText);
    console.log(data);
}

function reqError(err) {
    console.log('Fetch Error :-S', err);
}

var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();

Busca

Nossa solicitação de busca é mais ou menos assim:

fetch('./api/some.json')
    .then(
    function(response) {
        if (response.status !== 200) {
        console.log('Looks like there was a problem. Status Code: ' +
            response.status);
        return;
        }

        // Examine the text in the response
        response.json().then(function(data) {
        console.log(data);
        });
    }
    )
    .catch(function(err) {
    console.log('Fetch Error :-S', err);
    });

Começamos verificando se o status da resposta é 200 antes de analisar a resposta como JSON.

A resposta de uma solicitação fetch() é um objeto Stream, o que significa que, quando chamamos o método json(), uma promessa é retornada, já que a leitura do stream vai acontecer de forma assíncrona.

Metadados de resposta

No exemplo anterior, analisamos o status do objeto Response e como analisar a resposta como JSON. Outros metadados que podemos querer acessar, como cabeçalhos, estão ilustrados abaixo.

fetch('users.json').then(function(response) {
    console.log(response.headers.get('Content-Type'));
    console.log(response.headers.get('Date'));

    console.log(response.status);
    console.log(response.statusText);
    console.log(response.type);
    console.log(response.url);
});

Tipos de resposta

Quando fazemos uma solicitação de busca, a resposta recebe um response.type de "basic", "cors" ou "opaque". Esses types indicam a origem do recurso e podem ser usados para informar como tratar o objeto de resposta.

Quando uma solicitação for feita para um recurso na mesma origem, a resposta terá um tipo basic e não há restrições sobre o que você pode ver na resposta.

Se uma solicitação for feita para um recurso em outra origem que retorna os cabeçalhos CORs, o tipo será cors. As respostas cors e basic são quase idênticas, exceto pelo fato de uma resposta cors restringir os cabeçalhos que podem ser visualizados em Cache-Control, Content-Language, Content-Type, Expires, Last-Modified e Pragma.

Uma resposta opaque é para uma solicitação feita para um recurso em uma origem diferente que não retorna cabeçalhos CORS. Com uma resposta opaca, não é possível ler os dados retornados nem ver o status da solicitação, o que significa que não podemos verificar se ela foi bem-sucedida ou não.

É possível definir um modo para uma solicitação de busca de modo que apenas algumas solicitações sejam resolvidas. Os modos que podem ser configurados são os seguintes:

  • same-origin só funciona para solicitações de recursos na mesma origem. Todas as outras solicitações serão rejeitadas.
  • cors permite solicitações de recursos na mesma origem e em outras origens que retornam os cabeçalhos CORs apropriados.
  • cors-with-forced-preflight sempre executa uma verificação de simulação antes de fazer a solicitação real.
  • no-cors destina-se a fazer solicitações para outras origens que não tenham cabeçalhos CORS e resultem em uma resposta opaque, mas, conforme declarado, isso não é possível no escopo global da janela no momento.

Para definir o modo, adicione um objeto de opções como o segundo parâmetro na solicitação fetch e defina o modo nesse objeto:

fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
    .then(function(response) {
    return response.text();
    })
    .then(function(text) {
    console.log('Request successful', text);
    })
    .catch(function(error) {
    log('Request failed', error)
    });

Encadeamento de promessas

Um dos melhores recursos das promessas é a capacidade de encadeá-las. Para busca, isso permite que você compartilhe lógica entre solicitações de busca.

Se você estiver trabalhando com uma API JSON, será necessário verificar o status e analisar o JSON para cada resposta. É possível simplificar o código definindo o status e a análise do JSON em funções separadas que retornam promessas, liberando você para se preocupar apenas em processar os dados finais e o caso de erro.

function status(response) {
    if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
    } else {
    return Promise.reject(new Error(response.statusText))
    }
}

function json(response) {
    return response.json()
}

fetch('users.json')
    .then(status)
    .then(json)
    .then(function(data) {
    console.log('Request succeeded with JSON response', data);
    }).catch(function(error) {
    console.log('Request failed', error);
    });

Definimos a função status, que verifica o response.status e retorna o resultado de Promise.resolve() ou Promise.reject(), que retornam uma promessa resolvida ou rejeitada. Esse é o primeiro método chamado na cadeia fetch(). Se for resolvido, vamos chamar o método json(), que retorna novamente uma promessa da chamada response.json(). Depois disso, temos um objeto do JSON analisado. Se a análise falhar, a promessa será rejeitada e a declaração de captura será executada.

O melhor de tudo é que você pode compartilhar a lógica em todas as suas solicitações de busca, facilitando a manutenção, leitura e teste do código.

Solicitação POST

É comum que os apps da Web queiram chamar uma API com um método POST e fornecer alguns parâmetros no corpo da solicitação.

Para fazer isso, defina os parâmetros method e body nas opções de fetch().

fetch(url, {
    method: 'post',
    headers: {
        "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    body: 'foo=bar&lorem=ipsum'
    })
    .then(json)
    .then(function (data) {
    console.log('Request succeeded with JSON response', data);
    })
    .catch(function (error) {
    console.log('Request failed', error);
    });

Enviar credenciais com uma solicitação de busca

Para fazer uma solicitação de busca com credenciais como cookies, defina o credentials da solicitação como "include".

fetch(url, {
    credentials: 'include'
})

Perguntas frequentes

Como faço para cancelar uma solicitação fetch()?

No momento, não há como cancelar uma busca, mas isso está sendo discutido no GitHub (link em inglês). H/T @jaffathecake para esse link.

Há um polyfill?

O GitHub tem um polyfill para busca. H/T @Nexii por apontar isso.

Por que o "no-cors" é compatível com service workers, mas não com a janela?

O motivo é uma preocupação com a segurança. Saiba mais neste link.