Promises with Async/Await
Promises are one approach to asynchronous programming. A promise is an object that represents the eventual completion or failure of an asynchronous operation and its resulting value. Promises have three states: pending, resolved, or rejected.Functions which perform asynchronous operations (such as interacting with the file system or a database) can return a promise which is then resolved later. If that promise can't be kept (something is rejected) we can catch the error and deal with it.
Then and Catch
getDataFromFile()// promise is resolved.then((result) => {doSomethingWithData(result)})// promise is rejected.catch((err) => {handleError(err)})
This example is about as simple as it gets. We don't need to understand the exact details of how to return a promise in order to use getDataFromFile
. We just need to expect that:
The
then
function will calldoSomethingWithData
if there is no error.doSomethingWithData
can be passed a value.The
catch
function will callhandleError
if there is an error, andhandleError
will be passed an error (often a JavaScriptError
object).
If getDataFromFile
returns a resolved promise, then
will be called (and catch
won't be). If it returns a rejected promise, catch
will be called (and then
won't be).
If you are familiar with callbacks, you'll notice it's like a callback that has had the err
and the data
handling parts separated. The same thing might be written like this with callbacks:
getDataFromFileCallback(function(err, data) {if (err) {console.error(err.message)} else {console.log(data)}})
Promise Chains
We can also string together quite long 'promise chains' which define the order certain tasks should occur in:
getDataFromFile().then((uncheckedMessyData) => {return checkTheData(uncheckedMessyData)}).then((messyData) => {return modifyTheData(messyData)}).then((tidyData) => {displayTheData(tidyData)}).catch((err) => {handleError(err)})
So long as each function in the chain returns a data object, this will help ensure everything takes place in the correct order. For example, displayTheData
always gets called after modifyTheData
.
Async/Await
The async/await
syntax is a more concise way to work with promises. It was introduced in ECMAScript 2017 (ES8) to simplify the process of writing asynchronous code, making it look more like synchronous code.
Here's the original example from above using async/await
:
const fetchDataAndDoSomething = async () => {try {// promise is resolvedconst data = await getDataFromFile()doSomethingWithData(data)}catch err {// promise is rejectedconsole.log(err)}}
In the async/await
example:
- The
fetchDataAndDoSomething
function is marked with theasync
keyword, indicating that it returns a promise. - Inside the function, the
await
keyword is used to pause execution until the promise is resolved. This makes the asynchronous code look more like synchronous code. - The
try/catch
block is used for error handling, making it more readable and manageable.
You can also chain promises with async/await
. Here's the chained promise code from above written with async/await
:
const processDataFromFile = async () => {try {const uncheckedMessyData = await getDataFromFile();const messyData = await checkTheData(uncheckedMessyData);const tidyData = await modifyTheData(messyData);displayTheData(tidyData);} catch (err) {handleError(err);}}
In this refactored code:
- Each
.then()
block is replaced with anawait
statement, making the code more linear and easier to read. - The
try/catch
block is used to handle errors, similar to the.catch()
at the end of the original code. - The
await
keyword is used to wait for each asynchronous operation to complete before moving on to the next step in the process.
In summary, async/await
is a syntactic sugar built on top of promises, providing a cleaner and more readable way to work with asynchronous code. It simplifies the process of handling promises, especially when dealing with multiple asynchronous operations or when chaining promises.