Strawman: `Function.prototype.try` (or `Function.try`)

# Isiah Meadows (7 years ago)

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) or Function.prototype.try(thisArg, ...args). It works much like Function.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

# Jordan Harband (7 years ago)

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.

# Oriol _ (7 years ago)

Instead of thrown: false or thrown: false, why not type: "normal", type: "return" or type: "throw"? This is what completion records use.

# Sebastian Malton (7 years ago)

An HTML attachment was scrubbed... URL: esdiscuss/attachments/20170822/f217beb0/attachment

# dante federici (7 years ago)

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.

# kai zhu (7 years ago)

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.

  1. 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

  2. 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

  1. 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

  1. 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

# Mike Samuel (7 years ago)

On Tue, Aug 22, 2017 at 2:08 PM, Sebastian Malton <sebastian at malton.name>

wrote:

  1. 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.

  1. 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.

# Isiah Meadows (7 years ago)

@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:

  1. 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 lets into something not unlike vars, 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.

  1. 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