How to write a declarative JavaScript promise wrapper
Tags: javascript
Published: 2022-04-12 16:05:11

**Written by [Vijit Ail](https://blog.logrocket.com/author/vijitail/)✏️**

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](https://blog.logrocket.com/understanding-asynchronous-javascript/) 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:

```js
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:

```js
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.

## Promises in JavaScript

A [promise](https://blog.logrocket.com/understanding-promise-all-in-javascript/) 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.

```js
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, use the `.then ()` method to chain with the `promise`, as shown below:

```js
promise.then(resolvedData => {
  // do something with the resolved value
})
```

Similarly, in the case of a rejected value, the `.catch()` method is used:

```js
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.

```js
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:

```js
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 from multiple `async` functions can lead to something like this:

```js
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:

```js
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`.

```js
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:

```js
const promiser = (promise) => {
  return promise.then((data) => [data, null]).catch((error) => [null, error]);
};
```

We can see the utility usage below:

```js
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.

```js
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:

```js
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:

```js
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](https://codesandbox.io/s/great-pare-lmr09j?file=/src/index.js) 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:

```js
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:

```js
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!

---

##[LogRocket](https://logrocket.com/signup/): Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

[LogRocket](https://logrocket.com/signup/) allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

![LogRocket Dashboard Free Trial Banner](https://blog.logrocket.com/wp-content/uploads/2020/06/reproduce-javascript-errors.gif)


LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

[Try it for free](https://logrocket.com/signup/).