JavaScript Promise and Async/Await
- Published on
- Dale Larroder--4 min read
Overview
- What is a Promise?
- Visualization of a Promise
- What is Async/Await?
- Visualization of callback hell
- Async/Await allows us to write code like this:
- A Clean Approach
- Conclusion
What is a Promise?
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
A Promise is in one of these states:
- pending: initial state, neither fulfilled nor rejected.
- fulfilled: meaning that the operation was completed successfully.
- rejected: meaning that the operation failed.
Visualization of a Promise
What is Async/Await?
Async/Await was the result of callback hell
and nested promises from the old era
of JavaScript
Visualization of callback hell
const doSomeFetch = fetch('/some-api')
doSomeFetch()
.then((data) => doAnotherFetch(data))
.then((result) => doAnoterFetch(result))
.then((outcome) => probablyLastFetch(outcome))
But in the real world, it would look some something like this:
doSomeFetch().then((data) => {
doAnotherFetch(data)
.then((result) => {
doAnoterFetch(data, result)
.then((outcome) => {
probablyLastFetch(data, result, outcome)
.then((fin) => {
// Ah Finally!
})
.catch((e) => {
handleError(e)
})
})
.catch((e) => {
handleError(e)
})
})
.catch((e) => {
handleError(e)
})
})
Async/Await allows us to write code like this:
const data = await doSomeFetch()
const result = await doAnotherFetch(data)
const outcome = await doAnoterFetch(data, result)
const fin = await probablyLastFetch(data, result, outcome)
Looks cool right? But how about error handling? It would look like this:
let data, result, outcome, fin
try {
data = await doSomeFetch()
} catch (e) {
handleError(e)
}
try {
result = await doAnotherFetch(data)
} catch (e) {
handleError(e)
}
try {
outcome = await doAnoterFetch(data, result)
} catch (e) {
handleError(e)
}
try {
fin = await probablyLastFetch(data, result, outcome)
} catch (e) {
handleError(e)
}
You can also use a single try catch
for all of the promises if you don't care about catching the individual errors of each promises.
This looks better:
try {
const data = await doSomeFetch()
const result = await doAnotherFetch(data)
const outcome = await doAnoterFetch(data, result)
const fin = await probablyLastFetch(data, result, outcome)
} catch (e) {
handleError(e)
}
But it you want able to catch and identify which block threw an error and handle each case differently, we append a catch block to each async method, kind of similar to the promises way:
const data = await doSomeFetch().catch(handleError)
const result = await doAnotherFetch(data).catch(handleError)
const outcome = await doAnoterFetch(data, result).catch(handleError)
const fin = await probablyLastFetch(data, result, outcome).catch(handleError)
A Clean Approach
Building up on our previous examples, let's create a better error handler:
export async function fancyFetch(fetcher, ...args) {
try {
const data = await fetcher(...args)
return [data, null]
} catch (error) {
log(error)
return [null, error]
}
}
If we have data, we return an array with data as the first parameter and error as null, if we get and error, we return data as null and the error object.
Now to re-write up the async/await example using an array destructring syntax:
const [data, dataError] = await fancyFetch(doSomeFetch);
if(dataError) { // got the error context, do something! }
const [result, resultError] = await fancyFetch(doAnotherFetch, data);
if(resultError) { // got the error context, do something! }
const [outcome, outcomeError] = await fancyFetch(doAnotherFetch, data, result);
if(outcomeError) { // got the error context, do something! }
const [fin, finError] = await fancyFetch(probablyLastFetch, data, result, outcome);
if(finError) { // got the error context, do something! }
Conclusion
- If you don't care about handling errors for individual async calls, go with a single
try/catch
. - If you care about handling errors for individual async calls, but do not care about the things inside the context of the catch block, use
await doSomeFetch().catch(handleError)
; - If the answer for both of the above points were 'No', then the
fancyFetch()
approach should work for you.