Yes, exceptions can have codes. As far as I have experienced, their error instances don't do anything better than a plain object, and a non-exception error object can more clearly take a wide variety of shapes. It also limits potential confusion between errors or exceptions raised by your code and that of some dependency.
It is true that exceptions "bubble", and plenty of developers take advantage of that behavior. In my opinion, it is not helpful for errors that are expected to happen as part of normal application behavior. Bubbling of errors creates a sense of logical indirection. Errors as return values communicate that likely nothing " wrong" actually happened. Good communication through code reduces the amount of time developers spend using the wrong assumptions when debugging.
There is also no reason why errors in a return value can't be returned by the caller. When you learn to love objects/hashes as return values, you can get the same benefits of bubbling but with a more clear path to follow when getting a picture of where a value is coming from and why.
In the case of actual exceptions, like accessing a property on undefined, an error being thrown is appropriate because, like you say, it can be bubbled up. The nice thing about reserving exceptions for this sort of thing is you might get away with a single try-catch at the highest scope and have a single straight forward error modal for when an unexpected problem occurs. Then you can otherwise implement errors in your code without the possibility that they will escape through bubbling and trigger behavior that is not immediately obvious when following the path of execution.
This is not to say that the tradition of using errors and try-catch for normal application control flow is inherently bad. Its just another tool. I do subjectively believe they are counter productive when used that way, and encourage others to try something like my approach. I think we would benefit by making it a standard practice.
It is true that exceptions "bubble", and plenty of developers take advantage of that behavior. In my opinion, it is not helpful for errors that are expected to happen as part of normal application behavior. Bubbling of errors creates a sense of logical indirection. Errors as return values communicate that likely nothing " wrong" actually happened. Good communication through code reduces the amount of time developers spend using the wrong assumptions when debugging.
There is also no reason why errors in a return value can't be returned by the caller. When you learn to love objects/hashes as return values, you can get the same benefits of bubbling but with a more clear path to follow when getting a picture of where a value is coming from and why.
In the case of actual exceptions, like accessing a property on undefined, an error being thrown is appropriate because, like you say, it can be bubbled up. The nice thing about reserving exceptions for this sort of thing is you might get away with a single try-catch at the highest scope and have a single straight forward error modal for when an unexpected problem occurs. Then you can otherwise implement errors in your code without the possibility that they will escape through bubbling and trigger behavior that is not immediately obvious when following the path of execution.
This is not to say that the tradition of using errors and try-catch for normal application control flow is inherently bad. Its just another tool. I do subjectively believe they are counter productive when used that way, and encourage others to try something like my approach. I think we would benefit by making it a standard practice.