Skip to main content

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


Further reading