JavaScript is a single-threaded programming language, which means that it can only execute code synchronously or from top to bottom one line at a time. However, asynchronous programming was introduced to address this issue.
This core JavaScript concept enables a function to execute while waiting for other functions to finish executing. We use asynchronous functions to make API calls to the backend. We also use them to write and read to a file or database. This concept comes in handy for both server-side developers and client-side developers.
In this guide, we’ll demonstrate how to write declarative asynchronous function calls in JavaScript. We’ll also show how it helps make our code more readable and easier to maintain.
Declarative programming
Before diving into the code, let’s review the declarative programming pattern.
Declarative programming is a programming paradigm that generally shows the logic of the code but not the steps followed to get there. With this type of programming, it’s not generally obvious what’s going on behind the scenes.
Conversely, imperative programming requires writing step-by-step code, with each step explained in detail. This can provide helpful background for future developers who may need to work with the code, but it results in very long code. Imperative programming is often unnecessary; it depends on our objective.
Declarative programming can be achieved using inbuilt JavaScript methods. Declarative programming allows us to write code that is more readable and therefore, easier to understand.
For instance, with declarative programming, we do not need to use a for
loop to iterate over an array. Instead, we can simply use inbuilt array methods, like map()
, reduce()
, and forEach()
.
Here’s an imperative programming example, showing a function that reverses a string using a decrementing for
loop:
const reverseString = (str) => { let reversedString = ""; for (var i = str.length - 1; i >= 0; i--) { reversedString += str[i]; } return reversedString; }
But, why write ten lines of code when we can achieve the same solution with just two lines of code?
Here’s a declarative programming version of the same code, using JavaScript inbuilt array methods:
const reverseString = (str) => { return str.split("").reverse().join(""); }
This code snippet uses two lines of code to reverse a string. It’s very short and gets straight to the point.
promise
A promise is a JavaScript object that contains the results of an asynchronous function. In other words, it represents a task that has been completed or failed in an asynchronous function.
const promise = new Promise (function (resolve, reject) { // code to execute })
The promise
constructor takes one argument, a callback function also called the executor. The executor function takes in two callback functions: resolve
and reject
. If the executor function executes successfully, the resolve()
method is called and the promise
state changes from pending to fulfilled. If the executor function fails, then the reject()
method is called and the promise
state changes from pending to failed.
To access the resolved value, the .then ()
method is used to chain with the promise
as shown below:
promise.then(resolvedData => { // do something with the resolved value })
Similarly, in the case of a rejected value, the .catch()
method is used:
promise.then(resolvedData => { // do something with the resolved value }).catch(err => { // handle the rejected value })
async-await
When we have several nested callbacks or .then
functions, it often becomes difficult to maintain the code and its readability.
The async
keyword helps us define functions that handle asynchronous operations in JavaScript. Meanwhile, the await
keyword is used to instruct the JavaScript engine to wait for the function to complete before returning the results.
The async-await
syntax is just syntactic sugar around promises. It helps us achieve cleaner code that’s easier to maintain.
const getUsers = async () => { const res = await fetch('https://jsonplaceholder.typicode.com/users'); const data = await res.json(); return data; }
async-await
enables promises or asynchronous functions to execute in a synchronous manner. However, it is always good practice to wrap await
keyword with a try-catch
block to avoid unexpected errors.
Here’s an example where we wrap the await
keyword and the getUsers()
function in a try-catch
block, like so:
const onLoad = async () => { try { const users = await getUsers(); // do something with the users } catch (err) { console.log(err) // handle the error } }
Custom promise
wrapper
One of the reasons that async-await
is such an awesome feature in modern JavaScript is that it helps us avoid callback hell.
Still, handling errors of multiple async
functions can lead to something like this:
try { const a = await asyncFuncOne(); } catch (errA) { // handle error } try { const b = await asyncFunctionTwo(); } catch (errB) { // handle error } try { const c = await asyncFunctionThree(); } catch (errC) { // handle error }
If we add all the async
functions in one try
block, we’ll end up writing multiple if
conditions in our catch
block, since our catch
block is now more generic:
try { const a = await asyncFuncOne(); const b = await asyncFunctionTwo(); const c = await asyncFunctionThree(); } catch (err) { if(err.message.includes('A')) { // handle error for asyncFuncOne } if(err.message.includes('B')) { // handle error for asyncFunctionTwo } if(err.message.includes('C')) { // handle error for asyncFunctionThree } }
This makes the code less readable and difficult to maintain, even with the async-await
syntax.
To solve this problem, we can write a utility function that wraps the promise
and avoids repetitive try-catch
blocks.
The utility function will accept a promise
as the parameter, handle the error internally, and return an array with two elements: resolved value and rejected value.
The function will resolve the promise
and return the data in the first element of the array. The error will be returned in the second element of the array. If the promise was resolved, the second element will be returned as null
.
const promiser = async (promise) => { try { const data = await promise; return [data, null] } catch (err){ return [null, error] } }
We can further refactor the above code and remove the try-catch
block by simply returning the promise
using the .then()
and .catch()
handler methods:
const promiser = (promise) => { return promise.then((data) => [data, null]).catch((error) => [null, error]); };
We can see the utility usage below:
const demoPromise = new Promise((resolve, reject) => { setTimeout(() => { // resolve("Yaa!!"); reject("Naahh!!"); }, 5000); }); const runApp = async () => { const [data, error] = await promiser(demoPromise); if (error) { console.log(error); return; } // do something with the data }; runApp();
Now, let’s take a look at a real life use case. Below, the generateShortLink
function uses a URL shortener service to shorten a full-length URL.
Here, the axios.get()
method is wrapped by the promiser()
function to return the response from the URL shortener service.
import promiser from "./promise-wrapper"; import axios from "axios"; const generateShortLink = async (longUrl) => { const [response, error] = await promiser( axios.get(`https://api.1pt.co/addURL?long=${longUrl}`) ); if (error) return null; return `https://1pt.co/${response.data.short}`; };
For comparison, here’s how the function would look without the promiser()
wrapper function:
const generateShortLink = async (longUrl) => { try { const response = await axios.get( `https://api.1pt.co/addURL?long=${longUrl}` ); return `https://1pt.co/${response.data.short}`; } catch (err) { return null; } };
Now, let’s complete the example by creating a form that uses the generateShortLink()
method:
const form = document.getElementById("shortLinkGenerator"); const longUrlField = document.getElementById("longUrl"); const result = document.getElementById("result"); form.addEventListener("submit", async (e) => { e.preventDefault(); const longUrl = longUrlField.value; const shortLink = await generateShortLink(longUrl); if (!shortLink) result.innerText = "Could not generate short link"; else result.innerHTML = `<a href="${shortLink}">${shortLink}</a>`; }); <!-- HTML --> <!DOCTYPE html> <html> <head> <title>Demo</title> <meta charset="UTF-8" /> </head> <body> <div id="app"> <form id="shortLinkGenerator"> <input type="url" id="longUrl" /> <button>Generate Short Link</button> </form> <div id="result"></div> </div> <script src="src/index.js"></script> </body> </html>
Here’s the complete code and demo for your reference.
So far, the promiser()
function can wrap only a single async
function. However, most use cases would require it to handle multiple, independent async
functions.
To handle many promises we can use the Promise.all()
method and pass an array of async
functions to the promiser
function:
const promiser = (promise) => { if (Array.isArray(promise)) promise = Promise.all(promise); return promise.then((data) => [data, null]).catch((error) => [null, error]); };
Here’s an example of the promiser()
function used with multiple async
functions:
import axios from "axios"; import promiser from "./promiser"; const categories = ["science", "sports", "entertainment"]; const requests = categories.map((category) => axios.get(`https://inshortsapi.vercel.app/news?category=${category}`) ); const runApp = async () => { const [data, error] = await promiser(requests); if (error) { console.error(error?.response?.data); return; } console.log(data); }; runApp();
Conclusion
The solutions shared in this guide for writing declarative asynchronous function calls in JavaScript are ideal for most scenarios. However, there are additional use cases that you may need to consider. For example, you might want to only handle the expected errors and throw any exceptional error that occurs during the promise
execution.
There are tradeoffs to any approach. It’s important to understand and take them into consideration for your particular use case.
The knowledge shared in this article is a good entry point for creating more complex APIs and utility functions as you continue with your coding journey.
Good luck and happy coding!
The post How to write a declarative JavaScript <code>promise</code> wrapper appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/8zcqfn2
via Read more