Promises and Decidability in Asynchronous Error Handling

# Kevin Smith (12 years ago)

A well-known problem with Promises, as implemented in various Javascript libraries, is that program errors are silenced by default. Consider the following program, which simply makes an HTTP request and then prints out the HTTP response headers:

fetchUri("http://someauthority.com/").then(response => {
    for (let header of repsonse.heders) // Note the misspelling!
        console.log(header.key, header.value);
});

On line 2, the property "headers" is misspelled, which should cause the program to report an unhandled error. Under the current Promises specification, however, the error will be silenced and the program will end happily with success.

Various solutions have been proposed for dealing with this problem, such as:

  • Extending debugging tools so that unhandled rejections are visible through a specialized tab or view.

  • Using the garbage collector to determine when rejections can no longer be handled, and therefore constitute a program error.

  • Adding a done method to the promise type which propagates rejections as program errors.

While each of these approaches provides a partial solution to the problem, they are ultimately inadequate because they do not address the underlying cause.

The root cause of this issue is that, as currently specified, the problem of determining whether a particular runtime error has an associated handler is Turing undecidable.

This is not a desirable property for an error handling mechanism to have, and it is not a design choice that can be reversed at a later date.

In order to make error handling decidable, it is sufficient to require that an error handler must be attached to the promise within a well-defined window. One such window would be the lifetime of the currently executing user call stack.

The designers of Dart have made a similar decision with their Future API. In the following document, users are instructed to register error handlers "early":

www.dartlang.org/articles/futures-and-error-handling/#potential-problem-failing-to-register-error-handlers-early

A quick search on StackOverflow relating to Futures and "early" error handling in Dart yielded no results. This cursory evidence suggests that requiring early registration of error handlers is not a significant problem for Dart users.

In my view, it would be a mistake to standardize the undecidable error handling model of current Promises design.

Thanks,

# K. Gadd (12 years ago)

Requiring early registration prevents the use of futures as value containers; i.e. kicking off an operation and storing the Future somewhere so anyone can use it at a later date.

I agree that an improved error handling policy would be very, very good to have, though.

# Domenic Denicola (12 years ago)

A well-known problem with loops, as implemented in various programming languages, is that infinite loops are silenced by default. Consider the following program, which simply adds some numbers in a loop:

var sum = 0;
while (Math.random() < config.loopLimt) { // Note the misspelling!
  sum += Math.random();
}

On line two, the property "loopLimit" is misspelled, which should cause the program to report an infinite loop as the condition will never be true. Under the current programming language paradigm, however, the error will be silenced and the program will loop happily forever.

Various solutions have been proposed for dealing with this problem, such as:

  • Extending debugging tools to allow some visibility into the code currently running in your program, through a specialized tab or view.

  • Using CPU consumption to determine when a loop has locked up the program, and thus probably consistutes a program error.

  • Adding a safeWhile construct to the language which ensures you cannot loop more than 64K times.

While each of these approaches provides a partial solution to the problem, they are ultimately inadequate because they do not address the underlying cause.

The root cause of this issue is that, as currently specified, the problem of deciding whether a particular while loop terminates is Turing undecidable.

This is not a desirable property for a looping mechanism to have, and it is not a design choice that can be reversed at a later date.

In order to make loop termination decidable, it is sufficient to require that the loop condition must be become true within a well-defined window. One such window would be the current second.

The designers of regular expressions have made a similar decision with their string matching API. A quick search on StackOverflow relating to loops and "infinite" loops in regular expressions yielded no results. This cursory evidence suggests that requiring looping constructs to be finite is not a significant problem for regular expression users.

In my view, it would be a mistake to standardize the undecidable looping model of the current while loop design.


Which is all to say, being unable to Turing-decide how a programming construct will work is not a particularly noteworthy disqualifier, and indeed opens up extremely powerful features in most cases. I believe the same is true of the current promise design.

# Andrea Giammarchi (12 years ago)

wouldn't events better suit and better solve the problem ?

fetchUri("http://someauthority.com/").on('load', response => {

repsonse.heders() });

at least this is how it works in eddy.js and I've never had silent errors

in current specs would be an addEventListener() within the XHR object

# Kevin Smith (12 years ago)

Requiring early registration prevents the use of futures as value containers; i.e. kicking off an operation and storing the Future somewhere so anyone can use it at a later date.

One can always do that, provided that you register an error handler before your call stack is cleared.

# Domenic Denicola (12 years ago)

From: Domenic Denicola [domenic at domenicdenicola.com]

which should cause the program to report an infinite loop as the condition will never be true.

On the other hand, sometimes we forget how loops work entirely in a moment of CSS-induced amnesia, and feel dumb on public mailing lists. Um, these aren't the droids you're looping for...

# Kevin Smith (12 years ago)

Domenic,

First, your caricature of my position is patently ridiculous.

Second, can you or someone else offer a clear use case which requires undecidable error handling semantics? I have asked for examples several times and so far I haven't seen anything convincing. Usually that indicates that the supposed use case is exaggerated if not fictional.

# Benjamin (Inglor) Gruenbaum (12 years ago)

I don't think that's the same thing at all.

Detecting an infinite loop is extremely hard at most cases (and of course impossible at others. However instead of discussing the halting problem, I think what's bothering this guy is that .then does not throw an error when an error occurs within it even if that error is completely unhandled by the promise flow.

Different libraries that implement promises handle errors differently. For example bluebird writes the stack trace to stderr (or console.error) if the rejection is unhandled by the start of the second turn. I quote: "your quote doesn't work as expected and you open console and see a stack trace : nice".

On that topic, I'd appreciate some reading on promises being added to the spec of the language (since when does the ES language spec even deal with concurrency? I thought that was a part of the host environment.

# Domenic Denicola (12 years ago)

You have been given examples in several previous conversations, but have chosen to not find them convincing. I (and others) tire of rehashing this argument with you monthly.

I instead responded to the novel content of your post, which was some sort of attempt to claim that a language feature allowing Turing-undecidable control flow means it is not a desirable language feature. Now that I have shown how "patently ridiculous" such a claim is, I do not feel a need to switch the conversation back to your usual line of inquiry. Perhaps others will take you up on it.

# Andrea Giammarchi (12 years ago)

Kevin I have no idea which library you are using but if you do this:

    fetchUri("http://someauthority.com/").then(response => {
        for (let header of repsonse.heders) // Note the misspelling!
            console.log(header.key, header.value);
    }).then(Object, function error(e) {
      console.err(e);
    });

Wouldn't that solve your problem?

Another thing worth trying is a generic window.on('error') handler ? Wouldn't that notify you ? That does not solve the broken flow but it should be an exit point to at least debug or notify problems, am I missing/misunderstanding something?

Thanks for any extra hint, apologies if I cannot help further :-/

# Nathan Wall (12 years ago)

Domenic Denicola wrote:

...

+1

# Kevin Smith (12 years ago)

It has been shown that delayed registration, in general, is useful. However, it has not been demonstrated that delayed registration of a primary error handler is necessary.

If use cases have been provided, then please provide links. Otherwise, let's not use ad hominem in place of logic.