With JavaScript, some operations are asynchronous, and many of these asynchronous operations are signalled via promises.
For instance, fetching data from an API is an asynchronous operation; you need to wait until data from the API has been fully downloaded. So, invoking fetch
doesn't give you the data. Instead, it gives you a promise, from which you will need to have another function be called to receive said value, as the first parameter of that function.
With a promise, to get the result the instance that is available, you invoke the then
method, and pass in said function as the first parameter.
Here's an example:
const fetchPromise = fetch('http://example.com/some-api/');
fetchPromise.then(response => {
// Do something with `response` here.
//
// Note: the `response` object doesn't actually contain the data. You will
// need to invoke either `response.text()` to extract the data into a string
// object, or `response.json()` to extract the data into an object, if the
// incoming data is valid JSON.
});
With fetch
, we have access to a response
object.
But from the response
object, we will need to extract the value. And that is done via invoking either response.text()
, or response.json()
. Both of these methods will yield a promise!
So here's what the above code would look like if we wanted to extract the textual value of the response.
const fetchPromise = fetch('https://example.com/some-api/');
fetchPromise.then(response => {
const textPromise = response.text()
textPromise.then(value => {
// The paramter `value` will be the extracted string.
});
});
But it gets better.
You know how in arrays, there's a flatMap
function, and it can accept as return value another array?
The then
method in promises acts like flatMap
, where you can return another promise, from the callback function in then
.
So, to extract the text value, you can invoke the above function like so:
const fetchPromise = fetch('https://example.com/some-api/');
fetchPromise.then(response => {
const textPromise = response.text();
return textPromise;
});
Above, we merely returned the promise. But how do we extract the value?
Before going into that, also note this important fact: the then
method will always return a promise!
And that promise will—at a high level—be exactly equal to what is returned by the callback in the then
.
So, to extract the text, the above code would look like so:
const fetchPromise = fetch('https://example.com/some-api/');
fetchPromise.then(response => {
const textPromise = response.text();
return textPromise;
}).then(text => {
// The value will be in `text`
});
Since we've established where promises typically come from, let's shorten the above code.
fetch('https://example.com/some-api/')
.then(response => response.text())
.then(text => {
// The value will be in `text`
});
Let's say the above API yields a string, and we can use that string to invoke another API call. Let's do that.
We can have numerous ways to do that.
We can nest the invocations.
fetch('https://example.com/some-api/')
.then(response => {
return response.text()
.then(text => {
return fetch(`https://example.com/some-api/{text}`)
.then(response => response.text());
});
})
.then(text => {
// The value will be in `text`
});
We can nest some of the invocations. Perhaps to group the "response-to-text extraction" logic.
fetch('https://example.com/some-api/')
.then(response => response.text())
.then(text => {
return fetch(`https://example.com/some-api/${text}`)
.then(response => response.text());
})
.then(text => {
// The value will be in `text`
});
Or have everything be sequential.
fetch('https://example.com/some-api')
.then(response => response.text())
.then(text => {
return fetch(`https://example.com/some-api/${text}`)
})
.then(response => response.text())
.then(text => {
// The value will be in `text`
});
Async functions
OK, the above invocation of then
is cumbersome, in many situations. So, a solution to limit the number of then
invocations would be to use an async
function.
An async
function looks like this:
async function something() {
return 42;
}
An async function doesn't merely return anything. It only returns a promise!
So, invoking something()
will yield a promise.
something()
.then((value) => {
console.log(value); // should print 42
});
It gets even better. An async function allows you to resolve promises without invoking then
. You would use the await
keyword for that.
So, for instance, if fetch
were to be invoked inside an async function, it would look like so:
async function doSomething() {
const response = await fetch('https://example.com/some-api');
return response.text();
}
Since async functions return a promise, we can have the above fetch invocations simplified to this:
fetch('https://example.com/some-api')
.then(async response => {
const text = await response.text();
const response2 =
await fetch(`https://example.com/some-api/${text}`);
return response2.text();
});
I don't know about you, but I'm not a fan of superfluous response
variables. A solution is to use then
to avoid creating those variables.
fetch('https://example.com/some-api')
.then(async response => {
const text = await response.text();
return fetch(`https://example.com/some-api/${text}`)
.then(response => response.text());
});
Top comments (1)
Great article, thanks