-
-
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. |
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.
In IRC, ljharb recommended sticking with good old promises (Approach number 4 above)