DEV Community

loading...
Cover image for Quick prototyping & playing with promises in one line

Quick prototyping & playing with promises in one line

aminnairi profile image Amin ・4 min read

Rationale

Promises are one of my favorite features of all the ECMAScript standards and provide a clever way of dealing with asynchronous results that can either be resolved or rejected.

But sometimes, when the source-code is growing, it can be tedious to work with, especially when error messages can be easily ignored.

If you don't care about the error messages, but rather the end result, you can provide a simple fallback value with this simple trick.

Context

Let's say you wanted to fetch the list of users from your API.

<!DOCTYPE html>
<html>
  <body>
    <script>
      "use strict";

      fetch("https://jsonplaceholder.typicode.com/users");
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

In this short example, we are using the Fetch API to ask our API for a list of users.

Of course, we need to deal with the success (resolved) and errors (rejected) cases.

<!DOCTYPE html>
<html>
  <body>
    <script>
      "use strict";

      fetch("https://jsonplaceholder.typicode.com/users").then(response => {
        return response.json();
      }).then(users => {
        console.log(users);
      }).catch(() => {
        console.error("Yep, no users.");
      });
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

In this particular example, we don't really care about the reason why it would reject, we simply want to provide a default value. We could do that in a more imperative way using an async function.

<!DOCTYPE html>
<html>
  <body>
    <script>
      "use strict";

      const main = async () => {
        let users = [];

        try {
          const response = await fetch("https://jsonplaceholder.typicode.com/users")
          users = await response.json();
        } catch {
          // ... discarded
        }

        console.log(users);
      };

      main();
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Here we are using an async function to imperatively handle each step of our promise. And if it fails, we simply have our default value that will kick in when we log the result.

This works well and as intended, but this is a lot of work for so little. Plus, we are using a try-catch with the catch part that is being discarded and is pretty much useless.

Let's see if we can find an alternative to all of this.

Alternative

Since the await keyword is used on a promise, nothing can stop you from writing all the promise instructions in one line and provide a default value right away.

<!DOCTYPE html>
<html>
  <body>
    <script>
      "use strict";

      const main = async () => {
        const users = await fetch("...").then(response => response.json()).catch(() => []);

        console.log(users);
      };

      main();
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Let's break this down real quick.

fetch("...");
Enter fullscreen mode Exit fullscreen mode

This is our promise. Nothing fancy, it will just fetch our data as earlier.

.then(response => response.json())
Enter fullscreen mode Exit fullscreen mode

This is the part where we handle any resolved value. This means that when the response can be turned into a JSON value, we will receive what's behind this call (here, the list of users).

.catch(() => []);
Enter fullscreen mode Exit fullscreen mode

This is the part where we handle the error. Here we simply say that instead of logging anything, we simply return a default value. Here it is an empty array so that it becomes easy to work with our data even if the request fails.

fetch("...").then(response => response.json()).catch(() => []);
Enter fullscreen mode Exit fullscreen mode

All of this is a single promise. This is important to understand because this is literally the heart of this technique. Because we have only one single promise here we are able to use what is coming next.

It will either reject and trigger the .then part, or fail and trigger the .catch part. You handled all possible cases in one line and whatever the outcome of the promise is, you know that you have a value for one or the other.

await fetch("...")...
Enter fullscreen mode Exit fullscreen mode

Here we simply make sure that anything that is being done on this line with the promise should be blocking the function until the promise is either resolved (the list of users) or rejected (the empty array).

If we put this all together, this means that in one line, you can easily request data from an API, tell it how you want it to be (either JSON or Text), and provide a default value in case it fails to fetch the data.

And this lets you use a nice two-liner for requesting and displaying any data from an API.

const users = await fetch("...").then(response => response.json()).catch(() => []);
console.log(users);
Enter fullscreen mode Exit fullscreen mode

Conclusion

This technique is very interesting because it lets you prototype things quickly, and even if you don't really need the error message.

If you are on a recent version of Node.js and using an ECMAScript Module, you can even leverage the new top-level await feature to make this a short little script.

$ npm install node-fetch
$ touch index.mjs
Enter fullscreen mode Exit fullscreen mode
import fetch from "node-fetch";

const users = await fetch("https://jsonplaceholder.typicode.com/users").then(response => response.json()).catch(() => []);

console.log(users);
Enter fullscreen mode Exit fullscreen mode
$ node index.mjs
[...] (output truncated)
Enter fullscreen mode Exit fullscreen mode

Be aware that any error messages will be hidden and so this technique is not well suited in a large application where you want to have controls and monitoring about what failed, and possibly file error reports to a third-party application like Sentry.

Also, the goal of this technique is definitively not to be clear and readable, if you are concerned about these points, you should be writing your promises using the classic syntax instead.

And of course, this technique is only usable in environments that support writing async functions so be aware of that if you are not transpiling your code.

Discussion (1)

Collapse
ajwcontreras profile image
Andrew Williams

Promises and error handling, such a tough topic to wrap my head around. While I love the simplicity of promises I do think it caused people to compromise significant performance . The previous syntax would at least encourage you to run Promise.all() or race properly because you receive simpler code in return. Async await is just too sleek not to use lmao

Forem Open with the Forem app