Promises
Handling Promise objects
fetch
calls are asynchronous and they return Promise objects. A promise is an object representing the completion of an asynchronous operation in future: the operation either succeeds (fulfilled) of fails (rejected).
The end result of a promise is not immediately available, and we do not know if the operation will succeed or not, nor do we know when it will be available. For that reason, promise objects need special handling:
- The handling needs to be launched when the value of the promise is available
- We need to handle both successful results as well as failures
JavaScript & TypeScript provides two ways to handle promises
then-catch
Promises can be handled using the Promise then
and catch
methods:
fetch('https://mydomain.com/api')
.then(response => response.json()) // handle Promise success, handling returns Promise
.then(data => { // handle the latter Promise
// process response
})
.catch(error => console.error(error)); // handle failures
When the promise resolves successfully, the result is passed as an argument to the handler function in the then
call. then
calls can be chained; the previous handler returns another promise. If the promise fails or there is any other error during the handling, the handler function passed in the catch
is called.
async-await
ECMAScript 2017 added a new syntax for handling Promise objects. The purpose of the addition was to make asynchronous code easier to write and to read afterwards by making async code look more like old-school synchronous code.
You have to put async
keyword in front of a function declaration thet returns a Promise instead of returning the value directly
await
can be put in front of any async function call to pause your code on that line until the promise fulfills, then return the resulting value
Errors are handled as exceptions, that is using try-catch
blocks
async-await example
fetchData = async () => {
try {
// execution is paused until the fetch call result is available
const response = await fetch('https://mydomain.com/api');
// execution is paused until the json call result is available
const data = await response.json();
}
catch(error) { // any error results in exception, handled here
console.error(error);
}
}
asynchronous code is within a try
block. If any failure occurs, it will be handled in the catch
handler. Note: await
only works inside async
functions! Even though the code looks like synchronous it is still asynchronous.
Using async-await in React code
Both syntaxes are equally valid and usable, you can use either. Note that the function passed to useEffect
may not be async
. If you need to call an async function with useEffect, pass it a sychronous function that calls your asynchronous function, e.g.
useEffect(() => { // regular non-async function passed to useEffect
// define async function that makes the asyncronous calls
const doFetch = async () => {
const result = await fetchData(); // call my async function
setData(result);
};
// call the async function
doFetch();
}, []);
Immediate invokation
You can do the same as previously also using immediate invokation (calling) of the newly declared function:
useEffect(() => { // regular non-async function passed to useEffect
// define async function expression that makes the asynchronous calls...
(async () => {
const result = await fetchData(); // call my async function
setData(result);
})(); // ...and call it immediately
}, []);
Because the precedence of the function call operator ()
is so high, the function expression (arrow function) needs to be in brackets