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

In the example of the following request:

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

On the first then shouldn't I get a full response from the server anymore? In the first then I have already tried to give a console.log, but I can't find where the JSON is inside the returned object. Only in the second then I get this data, which made me confused. If in the first then I do not have this information, why would I have in the second if it is a callback of the first?

Author: Luiz Felipe, 2020-05-20

3 answers

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

Com 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!...'));
 6
Author: Sergio, 2020-05-20 00:47:45

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

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

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

Therefore, since we are working with two promises (one returned by the fetch and another returned by the json method of Response), you have to thread them 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 one works with two promises, if using async/await, we would have to use the operator await twice, one to solve the promise of fetch and the other to solve 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 excerpts from the question:

On the first then shouldn't I get a full response from the server anymore?

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

If in the first then I do not have this information, why would I have in the second if it is 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. They're two different things.

See:

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 memory. From there, all you have to do is transform the stream into JSON. :)

I repeated several times a few things in this answer, I hope it did not get boring.

 5
Author: Luiz Felipe, 2020-05-20 01:21:38

The first then returns a promise(a Promise is an object that represents the eventual completion or failure of an asynchronous operation.) of type response where it is defined by the developer which type it wants to receive.

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

To make it clearer the code can be written in this way where it demonstrates that if the answer of the first then is satisfactory (resposta.ok) the next method of the aforementioned can be executed 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 it means that the second then is referring to the response of the primeiro and they are usually called one after the other appearing to be one thing. Using async/await can be summarized as follows:

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:

 2
Author: novic, 2020-05-20 01:34:10