5 ms·
I'm a big fan of the new operators. One of my remaining gripes with Javascript/Typescript is the try/catch mess around await. It makes assignment of const a pa
by reggieband 7y ago
I'm a big fan of the new operators.
One of my remaining gripes with Javascript/Typescript is the try/catch mess around await. It makes assignment of const a pain for async calls that may reject.
e.g.
let result: SomeType;
try {
result = await funcThatReturnSomeType();
} catch (err) {
doSomethingWithErr(err);
}
// at this point result is `SomeType | undefined`
if (result) {
doSomething(result);
}
I really want some kind of structures that allow me to make `result` constant. In some cases I've rolled my own Maybe/Either wrapper and then move the try/await/catch into a function but that is still a pain.
This is such a common pattern in my code ... I wish there was a more elegant way to deal with it.
- isthisreality 7y agoThere's this keyword called "var"
- 52-6F-62 7y agoI’ve run into the exact same situation and continue to repeatedly. In fact this is a stupidly common pattern in a library in working on at work right now. My solutions have involved casts (and comments explaining the assumptions involved) instead of the ‘if’ statement more times than I’d like to have done, but it ends up with the same result with the added (however small) compute with the conditional since it can be safe to assume the value is not undefined. It’s not perfect, but it at least omits unnecessary runtime code.
- cloverich 7y agoIts unnecessary until the clause above it changes and the assumption no longer holds. That's how I fight my urge to go with the cast.
- 52-6F-62 7y agoThat’s totally fair. Admittedly in my situation I have high degree of control over the input so my constraints are probably a little looser. At any rate, I’d appreciate the same thing you would in these cases.
- shtylman 7y agoCan you hoist the `if (result)` into the `try` part of the statement? (Without seeing more context hard to know why that wouldn't work for you). Another pattern to avoid the above is to remember that async functions return promises and that .catch() also returns a promise. So your above logic can be written as: const result = await funcThatReturnSomeType().catch(doSomethingWithErr); if (result) { doSomething(result); }
- btown 7y agoAnd if you hate the indentation from the `if (result) {}` you can combine this with the poor man's ? operator. const result = await funcThatReturnSomeType().catch(convertError); // result: SomeType | Error if (isError(result)) return result; // now result: SomeType EDIT: the ? operator in question - https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html https://doc.rust-lang.org/edition-guide/rust-2018/error-hand...
- nikeee 7y agoYou can also get rid of `if(result){}` by setting the return type of "doSomethingWithErr" to "never": function doSomethingWithErr(err: any): never { throw new Error("Oops"); } let result: SomeType; try { result = await funcThatReturnSomeType(); } catch (err) { doSomethingWithErr(err); } // because doSomethingWithErr has return type "never", result will be definitely assigned. doSomething(result); ..or just return in the catch block.
- desert_boi 7y agoInterestingly when I was encountering this myself recently, I discovered that JS finally blocks can return after a function has nominally already returned. Consider the following closure. (() => { try { // finally will return prior to this console.log console.log('this try was executed and the return ignored') return 'try block' } catch (e) { return 'error block' } finally { return 'finally block' } })()
- yawaramin 7y agoIIFEs are an option: const result = await (() => { try { return funcThatReturnSomeType(); } catch (err) { doSomethingWithErr(err); } })();
- smt88 7y agoThat is so much harder to read though...
- saurik 7y agoI don't think this is correct, as the try catch will only catch errors that happen while returning the promise, not awaiting it; you need to do this: const result = await (async () => { try { return await funcThatReturnSomeType(); } catch (err) { doSomethingWithErr(err); } })();
- yawaramin 7y agoYou may be right, proving the old adage that the best way to get an answer on the internet is to post a wrong answer ;-)
- jkoudys 7y agoIffys are a nice go-to in ts in general, because type inference is so handy and I'd rather not have a big pile of typed `let`s at the top that I have to assign into later. Even something as simple as a switch plays nicer if you iffy it and use it like an expression (similar to a match is rust)
- kushan2020 7y agoYou are unnecessarily wrapping it into Maybe. Promise.then/.catch is essentially what you want.
- evangow 7y agoI use await-to-js[1] to clean this stuff up. You can pair it with destructuring arrays like so: const [err, result] = await to(funcThatReturnSomeType()); if(err) doSomethingWithErr(err); doSomething(result); [1] https://www.npmjs.com/package/await-to-js https://www.npmjs.com/package/await-to-js
- snypox 7y agoGood old Go-style
- jkoudys 7y agoYep. Doesn't even feel so different from using error first callbacks. I had to go back to using them for something recently, and feel like most of my problems back then stemmed from excessive deep nesting, and overreliance on the closure scope. Don't get me wrong I love the Promise api, but if ts were popular pre-Promises, we wouldn't have had half the issues with callbacks for concurrency. You'd be able to type functions that take callbacks, know what errors they can receive, see if anything's out of scope or misused, etc
- jkoudys 7y agoThis right here is exactly why Errors should be enums, instead of this wacky exception handling system. Try/catch is why I frequently prefer plain old .then and .catch instead
- brlewis 7y agoAnother commenter pointed out that .catch is perfectly fine in the async/await world, even though you pretty much never use .then anymore. Here's another way of doing it: try { doSomething(await funcThatReturnSomeType()); } catch (err) { doSomethingWithErr(err); }
- Jasper_ 7y agoThey're not equivalent if doSomething might throw and you don't want to catch it.
- brlewis 7y agoRight, which goes back to the question shtylman asked about why doSomething couldn't be part of the try block. I'm tossing this form out there in case it helps. People sometimes forget that await can go wherever an expression goes.
- trevordixon 7y agoCould do something like this: async function test() { const promise = Math.random() < 0.5 ? Promise.resolve('wow') : Promise.reject('oops'); try { await promise; } catch (e) { console.log('caught'); return; } const result = await promise; console.log('result', result); } test();