Skip to content

Instantly share code, notes, and snippets.

@joeytwiddle
Last active August 8, 2023 09:30
Show Gist options
  • Save joeytwiddle/7027f75a413a2eae3561858c6f7de50d to your computer and use it in GitHub Desktop.
Save joeytwiddle/7027f75a413a2eae3561858c6f7de50d to your computer and use it in GitHub Desktop.
How do you write a top-level async function?
/*
* TLDR: I want to use await in a function, but I don't want my function to return a Promise.
*
* Solution: Use this inside the body of your function: Promise.resolve(async () => { ... }).catch(...);
*
* Original post follows. (I say original, but it has slowly grown longer and longer...!)
*
* ------------------
*
* I have been using async-await and loving it. But there is one thing I'm not sure how to handle.
*
* Sometimes I write a function which calls out to Promises, but it handles all the errors itself.
*
* Sounds fine. But because it is an async function, it returns a Promise, whether I want it to or not!
*
* It's a kind of dummy Promise, because it always resolves with undefined. I have already handled the actual
* result / error.
*
* So there is a Promise with no .then() or .catch().
*
* This feels like a violation of golden rule: "A Promise must either be returned or caught." This violation could
* potentially trigger warnings from linters.
*
* So ... how do you write code like this?
*
* Approach 1 provides an example. The other approaches are possible alternatives but they each suck.
*
* Do you just follow Approach 1 and abandon the golden rule?
*/
// Approach 0 - Recommended solution
async function foo () {
const result = await getSomethingSlowly();
console.log(`result:`, result);
}
foo().catch(console.error);
// After some time, this became my favourite solution.
// It's compliant and minimal, separating the concern from the function itself.
// Approach 1
async function foo () {
try {
const result = await getSomethingSlowly();
console.log(`result:`, result);
} catch (err) {
console.error(err);
}
}
// We must use the async keyword because we want to use await inside.
// But the async keyword means that a Promise will be returned.
// The returned Promise is actually useless, so there is really no point in handling it.
// But I feel uncomfortable not handling it. Technically it is a violation of the Golden Rule.
// And it could trigger some static analyzers to complain. (See WebStorm's linter complaining in the comments below.)
// Approach 2
function foo () {
Promise.resolve().then(async () => {
const result = await getSomethingSlowly();
console.log(`result:`, result);
}).catch(err => {
console.error(err);
});
}
// Feels non-idiomatic because we are back to using .catch()
// and also the Promise keyword
// But in the end, this is the solution I have settled on.
// Approach 3
function foo () {
(async function () {
const result = await getSomethingSlowly();
console.log(`result:`, result);
}()).catch(err => {
console.error(err);
});
}
// Well we got rid of the Promise.resolve() by using an IIFE, but this looks quite fugly!
// It is also quite easy to forget the `()` in my experience
// Approach 4 - Just using good old promises
function foo () {
getSomethingSlowly().then(result => {
console.log(`result:`, result);
}).catch(err => {
console.error(err);
});
}
// No promise is returned and there are no linter warnings.
// It seems traditional Promises are still good for some things that async-await is not!
// One difference (and disadvantage) here is that `getSomethingSlowly()` could throw an Error, instead of returning
// a rejected promise.
// For that reason, I often go with Approach 2 instead.
// Approach 99, included for completeness
async function foo () {
const result = await getSomethingSlowly();
console.log(`result:`, result);
}
foo();
// Sometimes it is guaranteed that a promise will never reject. (E.g. the classic `setTimeout` delay promise.)
// But if it is possible that `getSomethingSlowly()` could reject, then in Node >= 9 the code above is dangerous,
// because a rejection will crash your process.
// However in front end code, people will sometimes use code like that when rejections are exceptional, because
// (since 2017) any rejection will be automatically logged for the developer.
// Either way, code like that is still a violation of The Golden Rule, and could trigger linters.
@joeytwiddle
Copy link
Author

If I only need to call one async function, and I don't need its return value (no need for .then()) then I just use good old promises (Approach 4).

Otherwise, I use Approach 3. It is functionally the same as Approach 2, but is somewhat shorter, and its unique appearance makes it obvious why we are doing it. Approach 3 is an IIAFE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment