Strawman: `Function.prototype.try` (or `Function.try`)
I think if we wanted to add a "Result" type, we might want to do that holistically instead of having an arbitrary prototype method return an arbitrary object literal.
It'd be really nice to have that type, but I'm not sure how easy it'd be to make a compelling case.
Instead of thrown: false
or thrown: false
, why not type: "normal"
, type: "return"
or type: "throw"
? This is what completion records use.
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20170822/f217beb0/attachment
There exist lots of libraries that give this sort of functionality among others in the functional community -- lots of Either or Maybe implementations.
I don't think this is a healthy addition to the spec since you would then want to consider all the other monadic goodies like mapping over these values, folding, "all" and "some", etc.
There exist lots of libraries that give this sort of functionality among others in the functional community -- lots of Either or Maybe implementations.
I don't think this is a healthy addition to the spec since you would then want to consider all the other monadic goodies like mapping over these values, folding, "all" and "some", etc.
i agree. a 10-line vanilla es5 solution already exists for what i think are the expected use-cases.
-
the 10-line solution - function tryCatchOnError - kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.tryCatchOnError, kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.tryCatchOnError
-
use-case #1 - kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.jwtA256GcmDecrypt, kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.jwtA256GcmDecrypt
use tryCatchOnError to either a) try to return the decrypted jwt token, or b) return an empty object if an error was thrown during decryption
- use-case #2 - kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.ajax, kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.ajax
use tryCatchOnError in an asynchronous ajax function
- use-case #3 - kaizhu256.github.io/node-utility2/build..master..travis-ci.org/coverage.html/node-utility2/test.js.html, kaizhu256.github.io/node-utility2/build..master..travis-ci.org/coverage.html/node-utility2/test.js.html
use tryCatchOnError to test for exceptions
On Tue, Aug 22, 2017 at 2:08 PM, Sebastian Malton <sebastian at malton.name>
wrote:
- What is the difference between "normal" and "return"?
Noone's answered yet, so I'll take a stab.
An instruction that completes normally transfers control to the "next" instruction analogous to incrementing the program counter.
An instruction that returns pops (modulo yield) a stackframe which includes resetting the program counter to its state prior to the call, unless there are protected regions containing the program counter in which case it jumps to the innermost. Protected regions in EcmaScript correspond to try{...} for the most part.
- As previously stated a new type would probably be best since this can be made much better then what is essentially a try/catch. Since try catching is not very efficient it would be better to have some sort of type. However, this leads to how to implement, it could just be a class but then it could just be added manually. The real benefit to this would be great integration with async/await and pattern matching
Others are much more familiar with VM performance constraints, but IIRC,
its not the catching that is inefficient, but associating the stackframe
with the exception.
Dispatching an exception within a stack frame just involves finding the
innermost protected region containing the PC which is a binary search over
a fairly small list of instruction indices.
It sometimes matters whether you associate stack information with an Error
on new Error()
as in Java, or on throw as in Python but IIRC EcmaScript
doesn't yet take a position on that.
Throwing a non-Error-value also needn't incur the stack walk.
@all
BTW, I specifically avoided type: "normal"
, etc., because only
"normal"
and "throw"
are exposed to JS in any significant capacity
- they're what you can detect using
try
/catch
. The others are internal-only exception-like values used in the spec to implement breaking and loop continuation in its internal AST interpreter. Although I said completions, I meant it loosely.
(All browser engines instead operate on control flow graphs to
generate bytecode, so they also only really use "normal"
and
"throw"
.)
In addition, monads are a great way of introducing complexity into the language, and absent pattern matching at all (or anything else similarly extensible), let's save that rabbit hole for another time.
Here's some more specific responses inline:
On Tue, Aug 22, 2017 at 1:21 PM, Jordan Harband <ljharb at gmail.com> wrote:
I think if we wanted to add a "Result" type, we might want to do that holistically instead of having an arbitrary prototype method return an arbitrary object literal.
It'd be really nice to have that type, but I'm not sure how easy it'd be to make a compelling case.
I like this, but the complicated monadic whatnot that was immediately suggested is quite frankly not the ideal way to handle this, since we already have standard exception handling.
What about something like this, just to start? Of course, there's probably big things missing here, but just thought I'd get the ball rolling a little.
class Result<T> {
constructor(init: () => T);
value: any;
type: "return" | "throw";
get(): T; // throws if `init` threw
}
(I'd rather start small than wind up with something over-engineered and really bad.)
On Tue, Aug 22, 2017 at 2:08 PM, Sebastian Malton <sebastian at malton.name>
wrote:
- What is the difference between "normal" and "return"?
You get "return"
when returning from a function. The spec implements
early returns using this (a special exception-like completion), since
it just interpret's the AST directly.
Engines don't actually use this method, though. They build a control
flow graph, and they lower let
s into something not unlike var
s, so
they don't actually need to have anything like a special exception to
handle these cases.
Since try catching is not very efficient it would be better to have some sort of type.
V8 has been the exception, not the rule (no pun intended :-)) in having very poor exception handling performance. Every other engine has optimized them very well at this point. (Spidermonkey used to have issues with functions that threw exceptions, but that's not as much of a problem as it used to be, and it only deopt'ed once it actually threw.)
In addition, V8's new interpreter/compiler pipeline, Ignition and
TurboFan, no longer has this issue, and can fully optimize code with
try
/catch
in it.
On Tue, Aug 22, 2017 at 3:46 PM, Mike Samuel <mikesamuel at gmail.com> wrote:
Noone's answered yet, so I'll take a stab.
An instruction that completes normally transfers control to the "next" instruction analogous to incrementing the program counter.
An instruction that returns pops (modulo yield) a stackframe which includes resetting the program counter to its state prior to the call, unless there are protected regions containing the program counter in which case it jumps to the innermost. Protected regions in EcmaScript correspond to try{...} for the most part.
- As previously stated a new type would probably be best since this can be made much better then what is essentially a try/catch. Since try catching is not very efficient it would be better to have some sort of type. However, this leads to how to implement, it could just be a class but then it could just be added manually. The real benefit to this would be great integration with async/await and pattern matching
Others are much more familiar with VM performance constraints, but IIRC, its not the catching that is inefficient, but associating the stackframe with the exception. Dispatching an exception within a stack frame just involves finding the innermost protected region containing the PC which is a binary search over a fairly small list of instruction indices. It sometimes matters whether you associate stack information with an Error on
new Error()
as in Java, or on throw as in Python but IIRC EcmaScript doesn't yet take a position on that. Throwing a non-Error-value also needn't incur the stack walk.
es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss
Isiah Meadows me at isiahmeadows.com
Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com
Usually, it's most convenient to just let errors easily fall through, using
try
/catch
/finally
. But when you're writing more low-level error-handling code, especially where errors are never exceptional (like test errors in a testing framework or task errors in a distributed worker pool implementation), it's often easier to handle completions/results as if they were values, like how Lua 1 and Rust 2 handle it.Here's my proposal: introduce either
Function.try(func, thisArg, ...args)
orFunction.prototype.try(thisArg, ...args)
. It works much likeFunction.prototype.call
, except instead of returning/throwing, it always returns a{thrown, value}
pair.Polyfilling either would be easy:
Function.try = (func, thisArg, ...args) => { try { return {thrown: false, value: func.call(thisArg, ...args)} } catch (e) { return {thrown: true, value: e} } } Function.prototype.try = function (thisArg, ...args) { try { return {thrown: false, value: this.call(thisArg, ...args)} } catch (e) { return {thrown: true, value: e} } }
Engines could make this much more performant, though, by detecting the function, avoiding the object allocation if immediately destructured, optimizing the arguments like
Function.prototype.call
, and if called with a lambda, inlining the whole thing into a pseudo-try
/catch
.Isiah Meadows me at isiahmeadows.com
Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com