-
-
Save joeytwiddle/7027f75a413a2eae3561858c6f7de50d to your computer and use it in GitHub Desktop.
/* | |
* 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. |
In IRC, ljharb recommended sticking with good old promises (Approach number 4 above)
But I am quite in favour of Approach number 2 above. (Edit: See below for why I now prefer Approach 3.)
Starting a promise chain with Promise.resolve()
means that any synchronous error inside getSomethingSlowly
will be passed to the .catch()
.
With Approach number 4, a synchronous error inside getSomethingSlowly
would not be caught, and would crash the process!
So in fact Approach number 2 handles errors the same way Approach number 1 did.
A synchronous error would be something inside getSomethingSlowly
that went wrong before it starts its promise chain.
For example a mistake like: argument.prop
when argument is undefined, or a guard like throw Error("The args you passed are invalid");
On the other hand, we are very much fighting against the tide here.
So many places are taking Approach number 1, even without a try-catch, and relying on the engine to report unhandled rejections. (Example: react-navigation)
Perhaps we should acquiesce, and adopt approach 1 for the sake of consistency with the community!
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.
Here is an example linter warning. Usually this warning is very helpful. It sees me calling an async function without handling the returned Promise. It doesn't care that in this case I already handled the error inside the function.
So far I think I have the following options:
o1. Ignore the linter warning. (Not good practice IMHO.)
o2. Silence the linter warning for that line:
//noinspection JSIgnoredPromiseFromCall
in WebStorm (This is actually my favourite!)o3. Use one of the fugly solutions above.
o4. Handle the promise like I am supposed to:
foo().catch(console.error)
In this case, I may as well not bother catching the error inside the function. This option sucks if many places callfoo()
.