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?
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!...'));
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.
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: