Blindly casting the type to E is not "type safe" because of the potential for anything to be thrown in JS and for any downstream library to throw an error. Errors being `unknown` *is* the only type safe option. It is completely misleading and therefore the opposite of "type safe" to let the error be whatever is passed in via generic. More realistically if your safeAwait checked for certain likely/expected errors and would only return those in the tuple, else throw on unexpected errors, then you could say it was type safe, because you would be avoiding type not-safety lies to yourself/colleagues/codebase. Another approach could be wrapping the error in a custom type (there are more options now thanks to `Error.cause`) and that could have some logic to help with keeping safeAwait() actually type safe. Otherwise this is a dangerous lesson!
Yes, javascript lets you "throw" whatever you want. You can use `instanceof` in your catch block to see if it's an instanceof Error and handle that, and if it's not Error, throw a new Error wrapping whatever you got from the catch.
There is no way I'm ever gonna be able to use something like Effect at work, anywhere. You pretty much never start a project from 0 and when you do reviewers want you to reduce complexity for the uninitiated. Standard library is the only way I'm ever gonna be able to use this
There's another reason this won't be accepted by TC39 that's a lot more fundamental than bikeshedding the syntax: the "error" value in the tuple could be falsy or even null or undefined, which means it cannot be safely used to determine whether the expression threw or not. This is a potentially huge (if rare) footgun.
I saw this in a yt shorts a year ago, I've been using this and how I overcame that problem was to map it to an object. So catch(e) return [{success:false, error:e}, null]. This will ensure that whatever your error value is, it will never be misinterpreted.
Hi Theo, there is a good explanation from the TS team as to why errors will not be typed. It basically says it wouldn’t help most of the time, given how errors are handled by the Js community.
A really disappointing part of Typescript is that it doesn't allow you to (or even better force you to) add errors to a functions signature explicitly. Try catch in itself isn't too bad, the issue is then trying to figure out what exactly you just got thrown. Was it an error? Something extending error? Something completely different? Who knows
I really which that a throws clause would be included in typescript, even if it only told me that the function throws at all. But given that it would be extremely confusing and kind of useless as long as there are libraries with type definitions without it I unfortunately dont believe it'll happen or get picked up by the community
Stuff like this makes me absolutely loathe working with js/ts. I am professionally using Rust for the last few years, and while the error system isn't perfect, it's SUCH a great step up from throw/catch and gives soo much of the benefits you mention, including self-writing documentation and perfectly resolved and specified matching on error types.
@@habong17359 Currently working as team lead in ecommerce and using rust for everything thats not directly visible to the customers. Internal web applications, a lot of data analysis and reporting, interfacing our various systems (like shopify, logistics system, order management, newsletter and customer management), utility scripts for data validation and early warning, backends for various extensions to our shop, a shopify theme multiplexer for i18n, more data analysis and reporting, Shopify Functions, and everything else that doesn't absolutely need to run on client browsers.
@@jesse9999999 While I agree that it's the best I've worked with so far, I've come across various shortcomings over the years that I really hope will get fixed or simplified eventually. Especially once you need to take the error types of various third party libraries and need to convert them into another error type you have no control over, things tend to get really verbose and dicey - like when writing auth for a web application: you need to handle errors from your db and the hashing system, and transform those into errors your web framework like actix can emit back to the clients. That can either get tedious, or reinvite bad style like throwing around panics instead of cleanly writing error handling. Crates like color_eyre ease a lot of the burden already, but It could still be improved.
@32:31 You don't need the explicit return type annotation (in the function signature) if you use "return [null, result] as [null, T];" and "return [error, null] as [E, null]" -- works like doing "as const" without also coercing the tuple into a Readonly. I also dabbled with a few TS-based guard and assertion functions to help with the "unboxing" aspect of telling TS what you're working with I'd share the code in a TS playground link but since RUclips doesn't like that...
Discriminated unions are just the best datastructure that just became popular in the last few years. There are so many things you can express with a discriminated union. Error handling, implementation variation, optional data, a result that is one of a fixed list of types... The possibilities are endless
I do like this. Found this on a blog many years ago. Since then, it become my default error handling pattern. const [success, error] = await fetch("/") .then((res)=> [res, null] as const) .catch((err: unknown)=> [null, err] as const) if (error !== null) { Toast.error('error'); return; } I like success at front. when writing code you consider success case more. I'll add error handling later on.
Yip, I also read something along those lines and adapted it to this over time. const surePromise = promise => promise .then(result => ({ ok: true, result })) .catch(error => ({ ok: false, error })); USAGE: const myThingAttempt = await surePromise(myThingPromise()); if (myThingAttempt.ok) { const result = myThingAttempt.result; // happy path code here else { const error = myThingAttempt.error; // unhappy path code here }
I'm glad error handling is getting talked about. I built an NPM package called "ts-throws" that allows you to document a function's errors, providing consumers with a callback-style approach to handle each error's case individually. The benefit here is that the consumer doesn't need to import each error type, or make their own assertions to figure out which error is being thrown. Might be worth looking into.
The `safeAwait`/`mightFail` is wrong. `safeAwait` needs to take a function that returns `Promise`, not the promise directly. Then when using it, it would be `const [err, result] = await safeAwait(() => mightFail());`. The important part is to delay executing `mightFail()` until the execution context is inside `safeAwait`.
await stuff().then(r=>[r, null]).catch(e=>[null, e]) This pattern is useful sometimes, but not always the most readable, try/catch is useful when you want to handle things at a higher layer. I think all this fuzz would be better focused in more important things like passing named arguments, or improving destructive assignment by letting you extract specific items from an array without having to extract all others like const [ , ,a] = [1,2, 3]; currently you have to specify horrible placeholder variables that you cannot even repeat like: const [_, _z, a ] = [1,2,3]; horrible...
common W for rust. functions like std::fs::read_file() don't throw, they return Result, where the error represents any kind of IO error the file might throw
You are wayyyyy too generous in your appraisal of my trustworthiness. I know a lot about a few type-related niches and virtually nothing about all the other stuff developers use to be productive, so I'd be extremely wary of any opinions I happen to express that stray outside those subjects 🥸
I can't help but bring up this Golang joke: Golang always keeps two cups by the bed, one with water and one empty. Rust: "Why do you keep two cups here all the time?" Golang: "In case I wake up and want water." Rust: "So why the empty one?" Golang: "What if I wake up and don’t want water?"
I see the same arguments against Effect as I did for TypeScript (complexity, learning curve, etc). At work, we got on the Angular 2 bandwagon quite early, so was forced into TS. Absolutely no one would have predicted the wide adoption TS got when Angular started using it. I'm posting this for posterity.
I like the way Axios handles this with their isAxiosError(err) function, think more libraries could benefit from something similar. I think most people end up writing a handleError(err) function which deals with all the various ways an error can be thrown don't they 😅 Also, put on a lint rule in your app so you can't throw strings, you'll thank me later
Ive said it before and Ill say it again, kotlin getting rid of checked exceptions nullifies their nullification checking, trading one mysterious source of error for another rather than leaving checked exceptions and having both solved and aiding editor tools in alerting you about possible exceptions.
So this proposal is just silly because you can already write this functionality directly in vanilla JS without a language extension. There are even multiple ways to implement it. People have forgotten how to program.
Most things can be done in vanilla js, but must if the time the proposals are done so not everybody makes their own dungeon for something used by many, or worse, an npm micro library
@@AndyCormack Agreed the proposed syntax is slightly more ergonomic, and we might get some optimizations along with it - but JS is already incomprehensible to newcomers (even C/C++ veterans) and adding more syntax has a point of diminishing returns.
You know what else would fix error handling? Just getting the information that a function might throw. There are so many functions with unknown errors that can be thrown. Not documented anywhere and the only way to know is to encounter them.
I mean you can add '@throws ErrorTypeHere' in the type annotation, but it's doesnt do anything for intellisense. I also have never seen any lib I've used have that annotated.
But, as Theo mentioned in the video, while you can add all the throwing and throw annotations you want and document tf out of your function, anything upstream, notably in code you don't own, can error with who knows what. You literally cannot know.
The reason this happens is this reluctance to handle errors by most developers. People really want to write the happy path and not think about possible errors. But in my opinion, if you aren't thinking about where your code could fail, you aren't being a very good developer.
The problem with all the libraries is to enforce them. Developers will always go the shortest way, even in my “own” projects when I tried to be strict and using the libraries it never worked out as i simply forgot it sometimes. A VS code plugin at this point in combination with an librar could fix this..
try() as a builtin function-like syntax would also work, and it would feel less weird for it to return a tuple, like const [err, value] = try(await fetch(...)) . I don't know if keywords that look like a function call is a thing in JS though.
The problem of a try function is that it won't work, unless J's takes said function as a special keyword, and that's a mess it will be easier if it's try await promise, as a keyword
If javascript added adt's we could use results and the syntax to wrap to results. It's a great solution because in order to use the value you actually have to unwrap the result. Javascipt would benefit from adt's anyways. But we could also just add a specific result datatype.
If the only problem is just ? having a tight relationship with "null/undefined" , maybe it could be another operator like := (borrowing from Go but that's ok they won't be mad)
How about this: const [error, result] c= await fetch(...) with c standing for catch. And then you could optionally extend it to this: const [error, result, pending] c=3 await fetch(...) where 3 stands for "additionally, give the pending boolean as well". I would really love to have c=3 or even c==3 or c===3 if I feel like my code is really good, as valid syntax in my production JavaScript code.
What if I told you that “c=3”, “c==3” and even “c===3” are all already valid syntax in JavaScript? So you can be content putting them in your code today!
The biggest problem with this proposal is that there is no forced usage in JavaScript, like Go, and just ignoring it is valid to the language. So, this would be a breaking change to have proper support for a feature like this. I don’t see how it would be possible to add the ability to fail a script for a future usage of variables and constants.
I usually list all explicit throws for a function in the jsdoc using @throws. It would be nice if “function() throws …” worked like that instead of an exhaustive list of all errors.
2 месяца назад
I have a few things to say about this. I'm worried this take loses some important history. 1. Exceptions were invented so we don't have need to return and handle error values all the time: you can raise failure to a higher-level controller - that's not a problem, that's useful! Try coding in Go for a week and see how much you love writing an `if err != nil` check every other line only to do exactly what I just described with the error anyway. 2. "neverthrow's most powerful feature is safeTry" - in the video this is described as focusing on the happy path as if an error never happens. This has already been solved before: monads.
A good Flutter app : WalkScape (it's still in closed beta, but it's already good). I still hate Flutter, but the reason the dev used Flutter makes sense. He basically wrote his own game engine in Flutter. Spoiler: I've helped on the project, so I'm biased. But it's a really cool project.
I see that used all the time -- e.g. if a fetch response's status is not ok and the status is 400 or 422 then throw the response body JSON which contains field level errors from the server to apply to a form submission (for example). Also mind you this approach is all because errors in JS suck in general anyway -- can't beat em join em type thing.
What I usually do is handle errors in interceptors and return null as response if any error occurs. Works well when you have very well error handling on the backend but otherwise it sucks. The safe await thing theo mentioned is awesome and I feel shit why didn't I think of this before 😂
This feels like a "nobody uses it because of our poor support so we won't improve the support on it either" 🤷🏼♂️ "Nobody is using mass transportation so let's only build roads."
Once I used Rust for a while then I returned back again to frontend development and the moment I had to handle some errors using try-catch, I felt extreme withdrawals ngl.
RE the final part of video: Having to still manually declare a tuple as the return type for a `try Foo()` seems needlessly tedious to me. At that point, why not have the new syntax automagically return an "Errorable" (`Safe`) type? For any function that returns `T`, calling it with the new syntax could return a `Safe`, which would have an `error: Error` and a `value: T` field. It wouldn't solve much, but at least it'd improve on the awkward try-catch scope issue
I really don't get the proposal. 1. The real issue is that TypeScript can't infer if something throws. Just having that and the option to disallow unhandled errors would solve most of this. 2. The ?= operator is entirely unnecessary. We don't need new syntax, when the same thing can be accomplished with .try(it => [it]).catch(it => [, it]). If anything that should be a method you can call on promises (i.e. .withError() or .try())
The user wanted to make checked exceptions part of the language. That would be awful in JS given how JS is used. Some languages did require you to formally declare all exceptions thrown from a function and required you to either handle or declare that the caller will also throw the exception. Devs hated it because it's tedious and many are lazy.
I’m probably just dumb but I write lots of code where I return a generic Error rather than throw, and “enforce” callers to handle it by expressing that at the type layer. I could see some carefully thought out variation on this becoming a conventional pattern. But again I am dumb, and work in a 13 year old monolithic codebase where there are countless reinventions of wheels running amok.
We just need to be like the rest of STEM and have way more things defined for us before hand in which we always use the same things based on what we are working on.
I really wanna see a good mechanic for error handling in non-promise async stuff. You know, event handlers, timeouts, intervals etc. Because right now anything you put in timeout simply escapes any error handling (except for global handlers like window.onerror).
i just want to be able to throw inline created errors without writing a class and the errors join the functions type signature like in Effect and i can handle them somewhere in the caller tree. error signatures is already good but most of the time i want to throw one-off errors to differentiate between e.g. different JSON.parse calls or different fetch calls etc.
What about simpler functional solutions like folktale Either? I'm starting to machete through the functional jungle because of the error silliness. Maybe nothing and Either left also handles this case from what early information I understand on this. I would love a deep dive/hot take on your impressions of the FP stuff out there. Maybe you have done this before. The syntax () () () () can be ugly but I think there are formatting ways to get around it. In functional, there's a lot of mess around the 3 ways of calling/passing functions: nesting, chaining and currying next argument. If those were clearer, I might soon be fully new FP bro. lol Good take btw. Love how you covered this.
I strongly discourage to abuse this "Micro" library and rethrow error by wrapping the original one if you are doing server side JS. Every try catch comes with a slight but istantiating a new Error is a massive hit, a function that will throws new errors 10% of the times will perform between 2 to 5 times slower then no errors. The tuple syntax of Go doesn't work well in JS because once err has been defined in the function you cannot easily reassign it because in theory you should always do something like: let [res1, err] = ... let [res2, err] = ... but this will not work because err is already defined so you need to do something like err1, err2 or avoid destructuring. My suggestion is just to leverage union types: function foo() { if (ok) return res else return new BadResult("Something went wrong") // This is a custom class } const res = foo() if (res instanceof BadResult) { // error handling here } // ok status
Re the alternate syntax, why not `attempt` instead of try? This ensures that the current keyword isn't overloaded, and is a more "spoken" language metafor.
Hey @t3dotgg, have you considered this for error handling? const source = await db .select() .from(sourceTable) .where(eq(sourceTable.name, data.sourceName)) .limit(1) .then((res) => res?.[0]) .catch(() => null);
Functional programmers: Use standard ways like Monads. 😊 Don't get mad, JavaScript is evolving in a more functional way. Everyone has started using functional concepts like map, reduce and filter, closures, and many more in React.
I'd been pondering the same errors as values approach since picking up Go recently, if we're thinking of going that route why not just use Go's syntax? := isn't in the language at the moment I don't believe? That way there's no real confusion to be had as the assignment operator syntax case determines why it comes out as a tuple.
I'm pretty sure ??= is just a pretty way to write something thay is an if statement under the hood. Just so you know it doesn't change performance, it does the usual stuff but now it's a tiny operator.
I'd prefer having a `.safe` method on the Promise prototype over this syntax. The syntax looks like some kind of conditional assignment to me, doesn't feel intuitive.
Its funny, I love theo's videos, but I hate Next.js, and I enjoyed learning Go. I think using javascript for the backend was a huge mistake. And it has hurt software development because young devs are afraid of learning languages. I was one of them for a while. The truth is, learning a backend language is pretty much the same level of difficulty as learning node.js. the only thing is the other language will expand the way you think as an engineer, while just learning node, will lock you into one way of thinking. It is useful to learn node, but it would be better to learn a backend language as well. My problem is I have absolutely no idea which one to learn.
Just for clarity, every time the phrase "effin javascript" was said in this video it was in regards to typescript typing issues. great video and explanation.
It seems like you dislike the idea of extending syntax and I feel like that might mean you would prefer how C++ does things over a language like Rust, at least if you were to learn a systems programming language. I'm not really a fan of either `?=` or `[foo, bar] = try ...;` but I also dislike exceptions in general. However, that said, for my own language I have been working on giving users the ability to select which type of error handling they like. I personally prefer a sort of hybrid of the Go method of collecting the error from the function, but I opted to allow the user to ignore it. Of course, it'll likely never see widespread use because people expect a programming language to wipe their ass these days, and mine lays the responsibility where it should be, the programmer.
That's great, but then if we're writing the UI, we have to perform conditionally rendering for each result reading? That doesn't sound fun to me, personally I prefer the error boundary approach 😂
My solution for error handling is to not throw errors except when there's a clearly invalid/contradictory state. Instead, I return an enum value and make you do a `typeof` check or else you can't use my return type. Wrap error-prone calls like fetch() and fs.readFile() in try/catch blocks and even dig into source code and specs to figure out what errors can be thrown-but always include an "unknown error" result just in case!
I strongly disagree. Do you want to still be stuck with var? That shit was awful. Do you not want private variables? Or a more niche example, no bigints or object.is?
it has absolutely no sense to add new syntax for something that can be easily achieved by simple function. this is basically simple like: const { response, error } = await fetch().then(response => ({ response })).catch(error => ({ error }));
Can someone tell me why this is not a good idea? const result = await operation().catch(e => e); if (result instanceof Error) { console.error(result); return; }
This will work perfectly fine! I don't think Theo is as familiar with the ".try()" and ".catch()" methods, which is why he uses straight up "try-catch" in an ""async" function, which ironically is just syntactical sugar for Promises. Here is a syntax that does what you are doing, but in a more general/reusable way: DEFINITION: const surePromise = promise => promise .then(result => ({ ok: true, result })) .catch(error => ({ ok: false, error })); USAGE: const myThingAttempt = await surePromise(myThingPromise()); if (myThingAttempt.ok) { const result = myThingAttempt.result; // happy path code here else { const error = myThingAttempt.error; // unhappy path code here }
I highly doubt your mind would be changed by a good Flutter app. Flutter can be used to clone almost any UI app now and you wouldn't be able to tell the difference.
Unless nullable types prevent method/field selection then this is an Absolutely garbage take, a record or tuple with a null field doesn’t enforce checking for errors. Errors should be a sum type with two cases which forces to check for errors before you can extract the value
What about a `noexcept` keyword (not allowed to contain "throw" and can only call other `noexcept` functions) and you assume every other function can throw?
Too restrictive. Throwing is normal. This approach basically tells developers they're not allowed to use throw ever. Also, too many packages already exist that use "throw" all over the place, so this approach wouldn't suit the current environment.
this will give people from the callback era PTSD
I think the worst part about modern youtube is that 5 minute videos are made into 30 minute videos just because of the ads.
Or being dikhead
Nah, the worst is when someone take him time to shitpost instead of just keeping scrolling
Do you want him to create content for free?
check his previous video
@@AJCastello I want him to create content that isn't 75% filler. How is that a lot to ask?
Blindly casting the type to E is not "type safe" because of the potential for anything to be thrown in JS and for any downstream library to throw an error. Errors being `unknown` *is* the only type safe option. It is completely misleading and therefore the opposite of "type safe" to let the error be whatever is passed in via generic. More realistically if your safeAwait checked for certain likely/expected errors and would only return those in the tuple, else throw on unexpected errors, then you could say it was type safe, because you would be avoiding type not-safety lies to yourself/colleagues/codebase. Another approach could be wrapping the error in a custom type (there are more options now thanks to `Error.cause`) and that could have some logic to help with keeping safeAwait() actually type safe. Otherwise this is a dangerous lesson!
Yes, javascript lets you "throw" whatever you want. You can use `instanceof` in your catch block to see if it's an instanceof Error and handle that, and if it's not Error, throw a new Error wrapping whatever you got from the catch.
There is no way I'm ever gonna be able to use something like Effect at work, anywhere. You pretty much never start a project from 0 and when you do reviewers want you to reduce complexity for the uninitiated. Standard library is the only way I'm ever gonna be able to use this
Easy, become team leader/architect and enforce it
Easy, adopt incrementally :)
There's another reason this won't be accepted by TC39 that's a lot more fundamental than bikeshedding the syntax: the "error" value in the tuple could be falsy or even null or undefined, which means it cannot be safely used to determine whether the expression threw or not. This is a potentially huge (if rare) footgun.
Could have a third value in the tuple to say whether it errored to cover these rare edgecases
@@bjbr🤢
And try-catch is better?
@@akam9919 I absolutely like the idea behind this proposal but I don’t think TC39 would seriously consider adding it to the language due to this flaw.
I saw this in a yt shorts a year ago, I've been using this and how I overcame that problem was to map it to an object. So catch(e) return [{success:false, error:e}, null]. This will ensure that whatever your error value is, it will never be misinterpreted.
Hi Theo, there is a good explanation from the TS team as to why errors will not be typed. It basically says it wouldn’t help most of the time, given how errors are handled by the Js community.
A really disappointing part of Typescript is that it doesn't allow you to (or even better force you to) add errors to a functions signature explicitly.
Try catch in itself isn't too bad, the issue is then trying to figure out what exactly you just got thrown. Was it an error? Something extending error? Something completely different? Who knows
I really which that a throws clause would be included in typescript, even if it only told me that the function throws at all.
But given that it would be extremely confusing and kind of useless as long as there are libraries with type definitions without it I unfortunately dont believe it'll happen or get picked up by the community
Stuff like this makes me absolutely loathe working with js/ts. I am professionally using Rust for the last few years, and while the error system isn't perfect, it's SUCH a great step up from throw/catch and gives soo much of the benefits you mention, including self-writing documentation and perfectly resolved and specified matching on error types.
May I ask what you're working on using rust? Never came across many who actually use Rust in the professional field
How do you know if someone is a rust user? They tell you
i've written python, typescript and go professionally and still think rust has the best error handling i've used. such a pleasure to work with.
@@habong17359 Currently working as team lead in ecommerce and using rust for everything thats not directly visible to the customers. Internal web applications, a lot of data analysis and reporting, interfacing our various systems (like shopify, logistics system, order management, newsletter and customer management), utility scripts for data validation and early warning, backends for various extensions to our shop, a shopify theme multiplexer for i18n, more data analysis and reporting, Shopify Functions, and everything else that doesn't absolutely need to run on client browsers.
@@jesse9999999 While I agree that it's the best I've worked with so far, I've come across various shortcomings over the years that I really hope will get fixed or simplified eventually. Especially once you need to take the error types of various third party libraries and need to convert them into another error type you have no control over, things tend to get really verbose and dicey - like when writing auth for a web application: you need to handle errors from your db and the hashing system, and transform those into errors your web framework like actix can emit back to the clients. That can either get tedious, or reinvite bad style like throwing around panics instead of cleanly writing error handling. Crates like color_eyre ease a lot of the burden already, but It could still be improved.
@32:31 You don't need the explicit return type annotation (in the function signature) if you use "return [null, result] as [null, T];" and "return [error, null] as [E, null]" -- works like doing "as const" without also coercing the tuple into a Readonly. I also dabbled with a few TS-based guard and assertion functions to help with the "unboxing" aspect of telling TS what you're working with
I'd share the code in a TS playground link but since RUclips doesn't like that...
Discriminated unions are just the best datastructure that just became popular in the last few years. There are so many things you can express with a discriminated union. Error handling, implementation variation, optional data, a result that is one of a fixed list of types... The possibilities are endless
I do like this. Found this on a blog many years ago. Since then, it become my default error handling pattern.
const [success, error] = await fetch("/")
.then((res)=> [res, null] as const)
.catch((err: unknown)=> [null, err] as const)
if (error !== null) {
Toast.error('error');
return;
}
I like success at front. when writing code you consider success case more. I'll add error handling later on.
Yip, I also read something along those lines and adapted it to this over time.
const surePromise = promise =>
promise
.then(result => ({ ok: true, result }))
.catch(error => ({ ok: false, error }));
USAGE:
const myThingAttempt = await surePromise(myThingPromise());
if (myThingAttempt.ok) {
const result = myThingAttempt.result;
// happy path code here
else {
const error = myThingAttempt.error;
// unhappy path code here
}
been using await-to-js for years to handle this excited to see what this video gets into commenting a little early
I'm glad error handling is getting talked about. I built an NPM package called "ts-throws" that allows you to document a function's errors, providing consumers with a callback-style approach to handle each error's case individually. The benefit here is that the consumer doesn't need to import each error type, or make their own assertions to figure out which error is being thrown. Might be worth looking into.
That along with pattern matching would be wild
and pipe operator!!
The `safeAwait`/`mightFail` is wrong. `safeAwait` needs to take a function that returns `Promise`, not the promise directly. Then when using it, it would be `const [err, result] = await safeAwait(() => mightFail());`. The important part is to delay executing `mightFail()` until the execution context is inside `safeAwait`.
await stuff().then(r=>[r, null]).catch(e=>[null, e]) This pattern is useful sometimes, but not always the most readable, try/catch is useful when you want to handle things at a higher layer. I think all this fuzz would be better focused in more important things like passing named arguments, or improving destructive assignment by letting you extract specific items from an array without having to extract all others like const [ , ,a] = [1,2, 3]; currently you have to specify horrible placeholder variables that you cannot even repeat like: const [_, _z, a ] = [1,2,3]; horrible...
finalllly been waiting for this for years, only if we knew what functions will actually throw errors tho
common W for rust. functions like std::fs::read_file() don't throw, they return Result, where the error represents any kind of IO error the file might throw
My man is thinking of go in js
You are wayyyyy too generous in your appraisal of my trustworthiness.
I know a lot about a few type-related niches and virtually nothing about all the other stuff developers use to be productive, so I'd be extremely wary of any opinions I happen to express that stray outside those subjects 🥸
When he mentioned you I was sure that it was going to be about `throw undefined` but to your credit the bikeshed syntax is definitely a no-go.
language level exceptions are a mistake. Result is the only good way to handle errors
I can't help but bring up this Golang joke:
Golang always keeps two cups by the bed, one with water and one empty.
Rust: "Why do you keep two cups here all the time?"
Golang: "In case I wake up and want water."
Rust: "So why the empty one?"
Golang: "What if I wake up and don’t want water?"
I see the same arguments against Effect as I did for TypeScript (complexity, learning curve, etc). At work, we got on the Angular 2 bandwagon quite early, so was forced into TS. Absolutely no one would have predicted the wide adoption TS got when Angular started using it.
I'm posting this for posterity.
the breakup seems to be really going well
when you started to write your version of ?= i was like “HOLD THE FU** UP… YOU CAN OVERLOAD OPERATORS???!?!??!???!!111?!!!”
a boy* can dream
*(old af)
Effect seems very similar to neverthrow after you showed it - but it's less demanding in terms of how much you need to write with it to get benefits
I like the way Axios handles this with their isAxiosError(err) function, think more libraries could benefit from something similar. I think most people end up writing a handleError(err) function which deals with all the various ways an error can be thrown don't they 😅
Also, put on a lint rule in your app so you can't throw strings, you'll thank me later
Ive said it before and Ill say it again, kotlin getting rid of checked exceptions nullifies their nullification checking, trading one mysterious source of error for another rather than leaving checked exceptions and having both solved and aiding editor tools in alerting you about possible exceptions.
So this proposal is just silly because you can already write this functionality directly in vanilla JS without a language extension. There are even multiple ways to implement it. People have forgotten how to program.
Most things can be done in vanilla js, but must if the time the proposals are done so not everybody makes their own dungeon for something used by many, or worse, an npm micro library
I would argue it's more about ergonomics, but you have a valid point.
@@AndyCormack Agreed the proposed syntax is slightly more ergonomic, and we might get some optimizations along with it - but JS is already incomprehensible to newcomers (even C/C++ veterans) and adding more syntax has a point of diminishing returns.
They should have used something like ~=. That would've made a lot more sense and would not "clash" with nullability.
You know what else would fix error handling? Just getting the information that a function might throw. There are so many functions with unknown errors that can be thrown. Not documented anywhere and the only way to know is to encounter them.
I mean you can add '@throws ErrorTypeHere' in the type annotation, but it's doesnt do anything for intellisense. I also have never seen any lib I've used have that annotated.
@@Unxok that is checked exception, the problem is that it never works in Java
this is called checked exceptions and they are in Java but everyone hates them
It's like saying we don't need typescript, just document the types.
But, as Theo mentioned in the video, while you can add all the throwing and throw annotations you want and document tf out of your function, anything upstream, notably in code you don't own, can error with who knows what. You literally cannot know.
The reason this happens is this reluctance to handle errors by most developers. People really want to write the happy path and not think about possible errors. But in my opinion, if you aren't thinking about where your code could fail, you aren't being a very good developer.
The problem with all the libraries is to enforce them.
Developers will always go the shortest way, even in my “own” projects when I tried to be strict and using the libraries it never worked out as i simply forgot it sometimes.
A VS code plugin at this point in combination with an librar could fix this..
try() as a builtin function-like syntax would also work, and it would feel less weird for it to return a tuple, like
const [err, value] = try(await fetch(...))
. I don't know if keywords that look like a function call is a thing in JS though.
it makes sense and yes: import ... from ... vs import()
The problem of a try function is that it won't work, unless J's takes said function as a special keyword, and that's a mess it will be easier if it's try await promise, as a keyword
If javascript added adt's we could use results and the syntax to wrap to results. It's a great solution because in order to use the value you actually have to unwrap the result. Javascipt would benefit from adt's anyways. But we could also just add a specific result datatype.
If the only problem is just ? having a tight relationship with "null/undefined" , maybe it could be another operator like := (borrowing from Go but that's ok they won't be mad)
How about this:
const [error, result] c= await fetch(...)
with c standing for catch.
And then you could optionally extend it to this:
const [error, result, pending] c=3 await fetch(...)
where 3 stands for "additionally, give the pending boolean as well".
I would really love to have c=3 or even c==3 or c===3 if I feel like my code is really good, as valid syntax in my production JavaScript code.
What if I told you that “c=3”, “c==3” and even “c===3” are all already valid syntax in JavaScript? So you can be content putting them in your code today!
I commented about this proposal on your video about JS error handling a week or two ago, and the interim safeawait/tuple hack. 🤓
The biggest problem with this proposal is that there is no forced usage in JavaScript, like Go, and just ignoring it is valid to the language. So, this would be a breaking change to have proper support for a feature like this. I don’t see how it would be possible to add the ability to fail a script for a future usage of variables and constants.
I usually list all explicit throws for a function in the jsdoc using @throws. It would be nice if “function() throws …” worked like that instead of an exhaustive list of all errors.
I have a few things to say about this. I'm worried this take loses some important history.
1. Exceptions were invented so we don't have need to return and handle error values all the time: you can raise failure to a higher-level controller - that's not a problem, that's useful! Try coding in Go for a week and see how much you love writing an `if err != nil` check every other line only to do exactly what I just described with the error anyway.
2. "neverthrow's most powerful feature is safeTry" - in the video this is described as focusing on the happy path as if an error never happens. This has already been solved before: monads.
JavaScript devs don't learn how to use the tools they've been given, they just wait for someone to create a redundant wrapper around it
A good Flutter app : WalkScape (it's still in closed beta, but it's already good). I still hate Flutter, but the reason the dev used Flutter makes sense. He basically wrote his own game engine in Flutter. Spoiler: I've helped on the project, so I'm biased. But it's a really cool project.
The worst part is, that can throw anything (null, string, promise...), not just Error
I see that used all the time -- e.g. if a fetch response's status is not ok and the status is 400 or 422 then throw the response body JSON which contains field level errors from the server to apply to a form submission (for example). Also mind you this approach is all because errors in JS suck in general anyway -- can't beat em join em type thing.
no one should ever use Js..
@@WiseWeeabo JS is ok as long as you know all the footguns and landmines 🙂
At the end, all languages sucks.
I do wrap them on a class extending error if I ever do so.
Js should've wrapped throw values inside errors
What I usually do is handle errors in interceptors and return null as response if any error occurs. Works well when you have very well error handling on the backend but otherwise it sucks. The safe await thing theo mentioned is awesome and I feel shit why didn't I think of this before 😂
This feels like a "nobody uses it because of our poor support so we won't improve the support on it either" 🤷🏼♂️
"Nobody is using mass transportation so let's only build roads."
this
Funny i made that method Theo wrote already 5 years ago! It does make stack traces a bit more confusing at times though
try/catch sux in JS because you can't constrain a given type to catch your exception, unlike C++, PHP or Java
Once I used Rust for a while then I returned back again to frontend development and the moment I had to handle some errors using try-catch, I felt extreme withdrawals ngl.
Crap like this is why I love Typescript.
TypeScript doesn't solve this
I hope you understand that this is Either, essentially
I'm pretty darn sure Theo is very much aware of this
Would love a JS tips page from Theo he discusses in these videos. It's hard to bookmark and revisit code in videos. Probably I will make such a page.
RE the final part of video: Having to still manually declare a tuple as the return type for a `try Foo()` seems needlessly tedious to me. At that point, why not have the new syntax automagically return an "Errorable" (`Safe`) type? For any function that returns `T`, calling it with the new syntax could return a `Safe`, which would have an `error: Error` and a `value: T` field. It wouldn't solve much, but at least it'd improve on the awkward try-catch scope issue
I really don't get the proposal.
1. The real issue is that TypeScript can't infer if something throws. Just having that and the option to disallow unhandled errors would solve most of this.
2. The ?= operator is entirely unnecessary. We don't need new syntax, when the same thing can be accomplished with .try(it => [it]).catch(it => [, it]). If anything that should be a method you can call on promises (i.e. .withError() or .try())
The user wanted to make checked exceptions part of the language. That would be awful in JS given how JS is used. Some languages did require you to formally declare all exceptions thrown from a function and required you to either handle or declare that the caller will also throw the exception. Devs hated it because it's tedious and many are lazy.
Let's just implement Rust's enums and Result/Option objects in javascript and call it a day ;)
"f'ing Javascript" when the _TypeScript_ checker is giving you a completely valid, reasonable, and helpful error is quite silly. Twice in one video...
I’m probably just dumb but I write lots of code where I return a generic Error rather than throw, and “enforce” callers to handle it by expressing that at the type layer. I could see some carefully thought out variation on this becoming a conventional pattern. But again I am dumb, and work in a 13 year old monolithic codebase where there are countless reinventions of wheels running amok.
The lenghts of which JS/TS developers have to go to have sane errors is insane.
We just need to be like the rest of STEM and have way more things defined for us before hand in which we always use the same things based on what we are working on.
I really wanna see a good mechanic for error handling in non-promise async stuff. You know, event handlers, timeouts, intervals etc. Because right now anything you put in timeout simply escapes any error handling (except for global handlers like window.onerror).
Casting error in 'catch' to arbitrary generic argument is a big footgun 🦶🔫
i just want to be able to throw inline created errors without writing a class and the errors join the functions type signature like in Effect and i can handle them somewhere in the caller tree.
error signatures is already good but most of the time i want to throw one-off errors to differentiate between e.g. different JSON.parse calls or different fetch calls etc.
What about simpler functional solutions like folktale Either? I'm starting to machete through the functional jungle because of the error silliness. Maybe nothing and Either left also handles this case from what early information I understand on this. I would love a deep dive/hot take on your impressions of the FP stuff out there. Maybe you have done this before. The syntax () () () () can be ugly but I think there are formatting ways to get around it. In functional, there's a lot of mess around the 3 ways of calling/passing functions: nesting, chaining and currying next argument. If those were clearer, I might soon be fully new FP bro. lol Good take btw. Love how you covered this.
Here is a functional solution:
const surePromise = promise =>
promise
.then(result => ({ ok: true, result }))
.catch(error => ({ ok: false, error }));
USAGE:
const myThingAttempt = await surePromise(myThingPromise());
if (myThingAttempt.ok) {
const result = myThingAttempt.result;
// happy path code here
else {
const error = myThingAttempt.error;
// unhappy path code here
}
I strongly discourage to abuse this "Micro" library and rethrow error by wrapping the original one if you are doing server side JS. Every try catch comes with a slight but istantiating a new Error is a massive hit, a function that will throws new errors 10% of the times will perform between 2 to 5 times slower then no errors.
The tuple syntax of Go doesn't work well in JS because once err has been defined in the function you cannot easily reassign it because in theory you should always do something like:
let [res1, err] = ...
let [res2, err] = ...
but this will not work because err is already defined so you need to do something like err1, err2 or avoid destructuring.
My suggestion is just to leverage union types:
function foo() {
if (ok) return res
else return new BadResult("Something went wrong") // This is a custom class
}
const res = foo()
if (res instanceof BadResult) {
// error handling here
}
// ok status
Re the alternate syntax, why not `attempt` instead of try? This ensures that the current keyword isn't overloaded, and is a more "spoken" language metafor.
Hey @t3dotgg, have you considered this for error handling?
const source = await db
.select()
.from(sourceTable)
.where(eq(sourceTable.name, data.sourceName))
.limit(1)
.then((res) => res?.[0])
.catch(() => null);
Functional programmers: Use standard ways like Monads. 😊
Don't get mad, JavaScript is evolving in a more functional way. Everyone has started using functional concepts like map, reduce and filter, closures, and many more in React.
I'd been pondering the same errors as values approach since picking up Go recently, if we're thinking of going that route why not just use Go's syntax?
:= isn't in the language at the moment I don't believe? That way there's no real confusion to be had as the assignment operator syntax case determines why it comes out as a tuple.
I'm pretty sure ??= is just a pretty way to write something thay is an if statement under the hood. Just so you know it doesn't change performance, it does the usual stuff but now it's a tiny operator.
I'd prefer having a `.safe` method on the Promise prototype over this syntax. The syntax looks like some kind of conditional assignment to me, doesn't feel intuitive.
Its funny, I love theo's videos, but I hate Next.js, and I enjoyed learning Go. I think using javascript for the backend was a huge mistake. And it has hurt software development because young devs are afraid of learning languages. I was one of them for a while. The truth is, learning a backend language is pretty much the same level of difficulty as learning node.js. the only thing is the other language will expand the way you think as an engineer, while just learning node, will lock you into one way of thinking. It is useful to learn node, but it would be better to learn a backend language as well. My problem is I have absolutely no idea which one to learn.
Just for clarity, every time the phrase "effin javascript" was said in this video it was in regards to typescript typing issues. great video and explanation.
damn I swear. them react youtubers are spying on my code^^
It seems like you dislike the idea of extending syntax and I feel like that might mean you would prefer how C++ does things over a language like Rust, at least if you were to learn a systems programming language. I'm not really a fan of either `?=` or `[foo, bar] = try ...;` but I also dislike exceptions in general. However, that said, for my own language I have been working on giving users the ability to select which type of error handling they like. I personally prefer a sort of hybrid of the Go method of collecting the error from the function, but I opted to allow the user to ignore it. Of course, it'll likely never see widespread use because people expect a programming language to wipe their ass these days, and mine lays the responsibility where it should be, the programmer.
Imagine now adding control flow to typescript types boilerplate xD. Thanks good i don't do front-end anymore
Inspired by Go, i do usually design my JS functions to be used like this, are there any drawbacks?
const [err, res] = await fn()
I have been doing this in Scala with a union and pattern matching to bubble up errors instead of using exceptions.
That's great, but then if we're writing the UI, we have to perform conditionally rendering for each result reading? That doesn't sound fun to me, personally I prefer the error boundary approach 😂
My solution for error handling is to not throw errors except when there's a clearly invalid/contradictory state. Instead, I return an enum value and make you do a `typeof` check or else you can't use my return type.
Wrap error-prone calls like fetch() and fs.readFile() in try/catch blocks and even dig into source code and specs to figure out what errors can be thrown-but always include an "unknown error" result just in case!
Agree, there always has to be unknown for it to be truly accurate typing in the context of JS, because you never know!
JavaScript needs to be reducing the number of ways to do the same thing, not add to it.
On spot!!
I strongly disagree. Do you want to still be stuck with var? That shit was awful. Do you not want private variables? Or a more niche example, no bigints or object.is?
@@einargsexactly, we don't want old websites to break, they just add new ways to do the things you want to do. It's what has happened to CSS as well.
Except you _can't_ reduce. Everything in web needs to be backwards compatible.
@@einargs bro said to deprecate the old not the new lol. At some point browsers should just stop supporting var and let devs seethe about it.
0:00 we can go further and say that "try/catch" errors in every language suck
I dont like an idea destructuring assignment in this case.
it has absolutely no sense to add new syntax for something that can be easily achieved by simple function. this is basically simple like: const { response, error } = await fetch().then(response => ({ response })).catch(error => ({ error }));
" i dont like to give go lang credit " lol so petty :D; but i agree.
There is something terribly wrong with trying to mimic exception handling as a Result pattern.
8:39 Could it be possible to implement a Zig style, try statement?
const [value, error] = try await promise;
Yip, if you develop the language you can make it do pretty much anything you want. The question is whether the ECMAScript board will let this through.
@@gustavstreicher4867 maybe I should rephrase then,
Is there a possibility where ECMA could consider this approach viable
When i use upload things with nest.js it didn't work it out ?
Can someone tell me why this is not a good idea?
const result = await operation().catch(e => e);
if (result instanceof Error) {
console.error(result);
return;
}
This will work perfectly fine! I don't think Theo is as familiar with the ".try()" and ".catch()" methods, which is why he uses straight up "try-catch" in an ""async" function, which ironically is just syntactical sugar for Promises.
Here is a syntax that does what you are doing, but in a more general/reusable way:
DEFINITION:
const surePromise = promise =>
promise
.then(result => ({ ok: true, result }))
.catch(error => ({ ok: false, error }));
USAGE:
const myThingAttempt = await surePromise(myThingPromise());
if (myThingAttempt.ok) {
const result = myThingAttempt.result;
// happy path code here
else {
const error = myThingAttempt.error;
// unhappy path code here
}
I highly doubt your mind would be changed by a good Flutter app. Flutter can be used to clone almost any UI app now and you wouldn't be able to tell the difference.
mans literally almost the Result/Either monad live
I also don't like the error first. You should be able to skip returning the error by using:
[res, _ ] = func()
After using Go I tend to replicate their error pattern in my codebase in other languages
I'm so early the other comments are just bots
Someone needs to write a lang that is effectively JS, but with all the bullcrap stripped out.
28:16 Ill prove you wrong about Flutter, just give me a few more months of development time
"not everyone has the upside down question mark" - International keyboard layout?¿?
Unless nullable types prevent method/field selection then this is an Absolutely garbage take, a record or tuple with a null field doesn’t enforce checking for errors. Errors should be a sum type with two cases which forces to check for errors before you can extract the value
*casually reinvents Rust
What about a `noexcept` keyword (not allowed to contain "throw" and can only call other `noexcept` functions) and you assume every other function can throw?
Too restrictive. Throwing is normal. This approach basically tells developers they're not allowed to use throw ever.
Also, too many packages already exist that use "throw" all over the place, so this approach wouldn't suit the current environment.
@@gustavstreicher4867 You're only not allowed to use throw within that function...