No Floating Promises: an eslint rule to prevent async code errors

irreverentmike

Mike Bifulco

Posted on December 13, 2021

No Floating Promises: an eslint rule to prevent async code errors

On my past few weekend Twitch streams (twitch.tv/irreverentmike by the way) I've been working on a browser-based guitar tuner, to make usre of silly domain name I bought a year or so ago, guithub.org.

Working with Web APIs for Audio is super interesting, and has given me an opportunity to research and learn about lots of great stuff that's built into modern web browsers that I hadn't used much before, like the Canvas API and the Web Audio API.

It also requires me to use lots of asynchronous code. Both Web Audio and Canvas require async to function, and as a result I've been using a lot of promises in my code. As I write and refactor the code for my pet project, I've found myself running into lots of errors relating to the setup and use of async stuff.

The basics of async / await in JavaScript

Executing code with async / await in JavaScript code requires a small amount of setup. At its most basic, it looks like this:

// Functions which use await to execute code must be declared with the "async" keyword
async function foo() {
  return await bar();
}

// written another way
const foo = async () => {
  await bar();
};
Enter fullscreen mode Exit fullscreen mode

The async keyword is used to adorn the parent function, to let JavaScript know that somewhere inside the function you're going to be awaiting something from another function call.

The await keyword is used to tell JavaScript that the function you're calling on that line is asynchronous, and that it will be waiting for something to happen before it can continue.

What happens when you forget to use async

Both of these ingredients are required for async / await to work, but drastically different things happen if you forget one or the other. If you forget to add async - it's very likely that your code won't run at all. Somewhere along the line, the JavaScript interpreter will crash, and tell you that you're trying to use await in a function that isn't marked as async.

What is a floating promise?

A floating promise is an async function that is called without use of the await keyword.

In many cases, if you forget to include await, your IDE/linter/interpreter won't fail at all, because you technically haven't done anything wrong. You can call an async function and not wait for it... this essentially creates a Promise but doesn't wait for it to resolve or reject. You'll effectively never hear back from it, and it may not even continue to execute.

I'll take an example of what this looks like from the docs page for eslint-plugin-no-floating-promise, which you can find on npm and GitHub:

async function writeToDb() {
  // asynchronously write to DB
}
writeToDb(); // <- note we have no await here but probably the user intended to await on this!
Enter fullscreen mode Exit fullscreen mode

When writeToDb() is called, it's not waiting for anything to happen, and it's not returning a Promise to the caller. Instead, the app will continue on its merry way without necessarily throwing any exceptions... and very likely without writing to the database at all.

It gets worse if you're relying on the return value from an async function:

async function createNewRecordInDb(input) {
  // asynchronously create new record in DB;
  let newRecord = await blah(input.name, input.email);

  return newRecord;
}

const entry = createNewRecordInDb({
  name: 'John Doe',
  email: 'foo@bar.com'
);

console.log('welcome to earth a brand new entry', entry)
Enter fullscreen mode Exit fullscreen mode

This is a problem, as the code operates assuming you've gotten back a value from a function that's actually still executing. This is called a floating promise, and it's a somewhat common mistake to make. It's a promise that is not being used by the rest of the code, so it's not being resolved.

If you use JavaScript: eslint-plugin-no-floating-promise Saves the day

As mentioned above, the eslint-plugin-no-floating-promise rule is a great way to make sure you don't accidentally forget to use await in your async functions. If you're working in JavaScript and your project already uses eslint, adding eslint-plugin-no-floating-promise is as easy as adding the plugin to your .eslintrc config file:

{
  "plugins": ["no-floating-promise"]
}
Enter fullscreen mode Exit fullscreen mode

and then adding the rule to your rules object:

{
  "rules": {
    "no-floating-promise/no-floating-promise": 2
  }
}
Enter fullscreen mode Exit fullscreen mode

You can see more details in the docs for eslint-plugin-no-floating-promise.

If you use TypeScript: @typescript-eslint/no-floating-promises already exists!

If you're working in TypeScript, there's already a handy solution baked into @typescript-eslint - just activate the rule @typescript-eslint/no-floating-promises and you're good to go!

{
  /* ... */
  "rules": {
    "@typescript-eslint/no-floating-promises": "error"
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

This is a really great way to protect yourself from an asynchronous programming issue in JavaScript and Typescript that can be extremely frustrating to debug if you're not actively looking for it. While suffering through finding floating promises in your code may be one way to learn about async / await in JavaScript, it's probably not a great use of your time, and setting up a quick lint rule can save you time, frustration, and maybe a broken keyboard or two.

More Reading

Note: The cover image for this post is based on a photo by Praveen Thirumurugan on Unsplash

πŸ’– πŸ’ͺ πŸ™… 🚩
irreverentmike
Mike Bifulco

Posted on December 13, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related