Why do I need to spend two then on AJAX requests made with the fetch API?

Asked

Viewed 195 times

10

In the example of the following request:

fetch("https://viacep.com.br/ws/01001000/json/")
  .then(resposta => resposta.json())
  .then(json => console.log(json));

In the first then should not already receive a complete response from the server? In the first then I already tried to give a console.log, but I can’t find where JSON is inside the returned object. Only in the second then I receive this data, which left me confused. If in the first then I don’t have that information, why would I have on the second if he’s a callback of the first?

3 answers

6

The return response of the fetch is a stream, and to extract its contents (Body.json() in API) is used response.json() which is asynchronous and returns a Promise itself. So or if it uses async/await or two .then...

With async/await:

fetch('https://jsonplaceholder.typicode.com/todos/')
  .then(async response => {
    const dados = await response.json();
    console.log('Tudo correu bem!');
    console.log('Qtd de dados:', dados.length);
  })
  .catch(err => console.log('Houve um erro!', err))
  .finally(() => console.log('The end!...'));

Or using 2 .then:

fetch('https://jsonplaceholder.typicode.com/todos/')
  .then(response => response.json())
  .then((dados) => {
    console.log('Tudo correu bem!');
    console.log('Qtd de dados:', dados.length);
  })
  .catch(err => console.log('Houve um erro!', err))
  .finally(() => console.log('The end!...'));

5

To API fetch returns a Promise with a Response, which contains various data relating to the response to the request, such as status, URL or the body itself (body).

However, the property body of Response is a ReadableStream, which would make its direct use relatively costly for an easy to use API. Thus, for convenience, Response also has some methods such as the json, text and others, that help in the conversion of the body, a ReadableStream, for more easily usable formats.

For this, these methods (such as json or text) should also return another Promise, since usually work with Streams is asynchronous.

Therefore, as we are working with two promises (one returned by fetch and another returned by the method json of Response), you have to chain them up using the then:

fetch('<url>') /* `fetch` retorna uma promessa. */
  .then /* Liga-se à resolução da promessa do `fetch`. */ ((response) => response.json() /* `json` retorna OUTRA promessa. */ )
  .then /* Liga-se à resolução da promessa método `json` de `Response`. */ ((data) => console.log(data))
  .catch(console.error);

To demonstrate that we work with two promises, if we use async/await, we would have to use the operator await twice, once to settle the promise of fetch and the other to settle the promise of json:

async function main() {
  // Espera a resolução da promessa do `fetch`.
  //               ↓↓↓↓↓
  const response = await fetch('<url>');

  // Espera a resolução da promessa do `json`.
  //           ↓↓↓↓↓
  const data = await response.json();

  console.log(data);
}

main().catch(console.error);

Thus, clarifying some parts of the question:

In the first then should no longer receive a complete response from the server?

Yes, and that’s exactly what’s happening. The fetch returns a Promise with Response, an object that contains several properties of the response - including its body. Despite this, the body is not yet in the format we want, which makes it necessary to use methods such as the json: to convert body, a Stream, in a JSON.

If in the first then I don’t have that information, why would I have on the second if he’s a callback of the first?

The second then is by no means a callback of the first. The second then is a tool to wait for method resolution json present in Response. Are two different things.

Behold:

fetch('https://api.github.com/users')
  .then((response) => {
    console.log('Propriedades e métodos de `response`:');
    console.log(Object.keys(response.__proto__));
    
    // Note que deste `then` estamos retornando uma outra promessa.
    // Isso porque este método `json` retorna uma OUTRA `Promise`.
    return response.json();
  })
  .then((data) => {
    console.log('Agora sim temos o nosso JSON "parseado":');
    console.log(data.length); // Número de dados retornados pela API. Não interessa para a explicação.
  });

It is worth saying that as soon as the first then is executed, that is, the promise of fetch is resolved, the request has already been completed and its body is in the browser’s memory. From there, all that remains is to transform Stream into JSON. :)

I repeated several times some things in this answer, I hope you didn’t get bored.

2

The first then returns a promise(A Promise is an object that represents the eventual completion or failure of an asynchronous operation.) of the kind Response where is set by the developer which type you want to receive.

That one Response has methods and properties and to define what is the return type the developer chooses some of the methods just below which is also a promise:

To make it clearer the code can be written this way where it demonstrates that if the answer of the first then is satisfactory (resposta.ok) can be executed the next method of the charges and that in the case was json(), example:

fetch("https://viacep.com.br/ws/01001000/json/")
 .then(resposta => {
    if (resposta.ok) { // deu certo a requisição
        resposta.json().then(json => console.log(json))
    }
});

That means the second then refers to the response of primeiro and are usually called one right after the other appearing to be one thing only. Using async/await can be summed up like this:

async function source() {
    try {
       const res = await fetch("https://viacep.com.br/ws/01001000/json/");
       if (res.ok) { // requisição deu certo ...
          const json = await res.json();
          console.log(json);
       }
    } catch (e) {
       console.log(e);
    }
}

References:

Browser other questions tagged

You are not signed in. Login or sign up in order to post.