Can we improve async JavaScript error handling?

Lars Eidnes larseidnes at gmail.com
Mon Dec 2 17:09:47 UTC 2019


> We already have a construct that automatically returns a promise, which
resolves on return and rejects on throw. It's called an async  function!

That's true, but marking a function as async doesn't (on its own) make it a
substitute for something like setTimeout.

On Mon, Dec 2, 2019 at 4:49 AM Frederick Stark <coagmano at gmail.com> wrote:

> We already have a construct that automatically returns a promise, which
> resolves on return and rejects on throw. It's called an async  function!
>
>
> On Nov 30 2019, at 7:05 am, Lars Eidnes <larseidnes at gmail.com> wrote:
>
> Currently when awaiting calls there's some surprising behavior when
> combining throw/catch and reject/resolve.
>
> ```js
>     function resolveAndThrow() {
>         return new Promise((resolve, reject) => {
>             resolve("huh");
>             throw new Error(""); //<-- this throw is executed
>         });
>     }
>     async function callAsync() {
>         try {
>             await resolveAndThrow();
>         } catch (e) {
>             console.log("caught error", e); //<-- yet, this is error
> logging never happens
>         }
>     }
> ```
>
> Here the error logging doesn't happen, because resolve() is called before
> the throw, which causes the thrown error to be swallowed. Specifically, the
> throw executes, and control flow is broken, but control flow never moves to
> the catch {} block. This is a sort of dark corner, where language
> mechanisms interact in conflicting ways. Most programmers wont have at
> top-of-mind which of resolve and throw will "win" in a situation like the
> one above. In fact, I suspect most JavaScript programmers haven't thought
> about this issue at all until they encounter it.
>
> I've written a longer post about this error handling issue in JavaScript
> some time ago: https://catchjs.com/Docs/AsyncAwait . There I mention how
> C# has async/await, but does not have reject/resolve, and thus doesn't
> really have this issue. There, exceptions thrown in async operations get
> transferred to the whomever is awaiting the Task, and whatever is returned
> from the Task is returned to whomever is awaiting it. So throw serves the
> purpose of reject(), and return serves the purpose of resolve().
>
> This is nice. It makes the reject/resolve mechanisms redundant, and
> removes the paradoxical resolve+throw case shown above. Really, this
> possible because the main interface for creating something awaitable (e.g.
> Task.Run(() => {}) ) is more similar to setTimeout than to new Promise().
> Is it possible to introduce similar mechanisms to JavaScript?
>
> For reference, C# Tasks can be used like this:
>
> ```
>     var result = await Task.Run(() => { Thread.Sleep(2); return 1; });
>     //result now holds 1
>
>     try {
>         var result2 = await Task.Run(() => {
>             throw new Exception(""); //This error is tracked across
> execution boundaries
>             return 1;
>         });
>     } catch (Exception e) {
>         Console.WriteLine(e); //This error logging happens, with a full
> stack trace from the await to the throw.
>     }
> ```
>
> The lack of resolve/reject has the benefit that there is no chance of
> messing up with mixing throw and resolve. A key thing here is that thrown
> exceptions are transported across execution contexts to wherever the Task
> is awaited.
>
> The reason we have reject/resolve, as far as I can see, is to be able to
> transport values and errors across execution context, for example so that
> one can do this:
>
> ```js
>     function resolveInSetTimeout() {
>         return new Promise((resolve, reject) => {
>             setTimeout(() => resolve(1), 100);
>         });
>     }
>     async function callAsync() {
>         var result = await resolveInSetTimeout(); //result now holds 1
>     }
> ```
>
> In JavaScript, the place where an API similar to Task could be useful
> would be as a replacement for setTimeout(callback). Some new API that takes
> a callback, returns a Promise, and where thrown errors in the callback are
> transported back to anyone awaiting the Promise, and where returned values
> in the callback resolve the Promise. It seems to me that transporting the
> thrown error across execution boundaries requires language/runtime support,
> at least if we want to get proper stack traces.
>
> If libraries with async APIs were built on this mechanism, we could use
> them and ensure that we can do error tracking for any errors happening
> inside setTimeouts. Now we're at the mercy of the library author
> remembering to catch the error and passing it to reject().
>
> I've written a lot here, and I suspect this mailing list is better
> equipped than I am to figure out what's a good idea in this context. Two
> questions to summarize:
>
> 1) Is it a good idea to introduce an alternative to setTimeout, where the
> distinction is that it returns a Promise, and that return/throw in the
> callback take the role of resolve/reject?
>
> 2) Are there other things we could do to minimize the need for
> resolve/reject?
>
> What do you think?
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20191202/d0e2e577/attachment.html>


More information about the es-discuss mailing list