Default Unhandled Rejection Detection Behavior #830

Closed
benjamingr opened this Issue Feb 13, 2015 · 135 comments

Comments

Projects
None yet
@benjamingr
Member

benjamingr commented Feb 13, 2015

With #758 (hopefully) soon landing to address #256 we will soon have unhandled promise rejections reported to the process via a process.on("unhandledRejection" event.

One issue that was deliberately deferred from that issue and PR is what the default behaviour of an unhandled promise rejection detected be.

Just to be clear - this is about the default behaviour of a rejected promise with no catch handler (or then handler with a function second argument) attached.

The different approaches

  • Logging the rejection to the console.
  • Throwing an error for process.on("uncaughtException" to deal with.
  • Causing the process to exit.
  • Do nothing by default.

There are several good arguments for each approach. I'll write down the arguments I'm aware of and will edit arguments as people make them in comments.

Logging

  • No silent failure by default.
  • Does not break any existing code - no "surprises". (See comment by @vkurchatkin on this)
  • Been a default in several userland libraries like bluebird and users have been very satisfied with it.

Throwing an error

  • A promise rejection is the asynchronous version of a thrown exception.
  • Does not ignore logical errors or leave the app in an inconsistent state.
  • False positives are extremely rare - and code causing them can arguably easily be refactored to not cause them by not attaching error handlers too late.

Process Exit

  • Differentiation between uncaught exceptions and promise rejections might be useful.

Do nothing

  • Reliable promise rejection detection is non-deciable, it's possible to report an unhandled rejection that actually is handled at a later point if the catch handler is attached at a very late time.
  • Does not break any existing workflows for anyone. No backwards compatibility issues for native promise users at all.
  • Does not make any assumptions on how people use native promises.

What do people think the default behaviour should be?

@vkurchatkin

This comment has been minimized.

Show comment
Hide comment
@vkurchatkin

vkurchatkin Feb 13, 2015

Member

Does not break any existing code - no "surprises".

Actually, logging to stdout (or stderr, I guess) can cause crash: nodejs/node-v0.x-archive#6612

Member

vkurchatkin commented Feb 13, 2015

Does not break any existing code - no "surprises".

Actually, logging to stdout (or stderr, I guess) can cause crash: nodejs/node-v0.x-archive#6612

@golyshevd

This comment has been minimized.

Show comment
Hide comment
@golyshevd

golyshevd Feb 13, 2015

Logging

IMHO emitting some event it is already enough logging. Direct writing to console is dirty and inconvenient to use with custom loggers, it may violate a format of application logs.
Sometimes i think about some built-in flexible logging facility (log levels, handlers, formatters, etc.) like logging in python

Throwing an error

Much more transparent way. An exception which was caught with promise but was not handled should be thrown. But it is painful breaking behaviour. Developers should promise.then(fn).then(null, noop) to explicitly ignore exceptions

Causing the process to exit.

Does thrown error will not be caught by uncaughtException event handler? Is not is the same as previous approach?
Do I understand correctly that the difference is that in one case the exception will be sent directly in the uncaughtException event handler, and in the second case the exception will be rethrown, but will still be caught in the uncaughtException handler if specified?

Do nothing

As variant to give developers choose how to handle unhandled promise rejections

Logging

IMHO emitting some event it is already enough logging. Direct writing to console is dirty and inconvenient to use with custom loggers, it may violate a format of application logs.
Sometimes i think about some built-in flexible logging facility (log levels, handlers, formatters, etc.) like logging in python

Throwing an error

Much more transparent way. An exception which was caught with promise but was not handled should be thrown. But it is painful breaking behaviour. Developers should promise.then(fn).then(null, noop) to explicitly ignore exceptions

Causing the process to exit.

Does thrown error will not be caught by uncaughtException event handler? Is not is the same as previous approach?
Do I understand correctly that the difference is that in one case the exception will be sent directly in the uncaughtException event handler, and in the second case the exception will be rethrown, but will still be caught in the uncaughtException handler if specified?

Do nothing

As variant to give developers choose how to handle unhandled promise rejections

@Havvy

This comment has been minimized.

Show comment
Hide comment
@Havvy

Havvy Feb 13, 2015

Contributor

Of those options, I'd argue for doing nothing. The end user can figure out what they'd like as a default, and add the handling there. If you want to be really strict about catching them, you can do process.on("unhandledRejection", function (err) { throw err; }). Personally, using Bluebird, I've only seen it once, and it was a false positive.

Contributor

Havvy commented Feb 13, 2015

Of those options, I'd argue for doing nothing. The end user can figure out what they'd like as a default, and add the handling there. If you want to be really strict about catching them, you can do process.on("unhandledRejection", function (err) { throw err; }). Personally, using Bluebird, I've only seen it once, and it was a false positive.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Feb 13, 2015

Member

I'd just like to emphasise that this discussion is about defaults - all options will remain open to developers and they'll still be able to decide all this.

@Havvy Thanks for your thoughts - I find those statistics very different from what bluebird users have been reporting. False positives are very rare but true positives are extremely common when people have typos or pass invalid JSON around etc.

@golyshevd - just to be clear - are you for throwing or for no default?

Member

benjamingr commented Feb 13, 2015

I'd just like to emphasise that this discussion is about defaults - all options will remain open to developers and they'll still be able to decide all this.

@Havvy Thanks for your thoughts - I find those statistics very different from what bluebird users have been reporting. False positives are very rare but true positives are extremely common when people have typos or pass invalid JSON around etc.

@golyshevd - just to be clear - are you for throwing or for no default?

@petkaantonov

This comment has been minimized.

Show comment
Hide comment
@petkaantonov

petkaantonov Feb 13, 2015

Contributor

The problem with doing nothing is that the feature might as well not exist as it will be undiscoverable to most developers.

IMHO emitting some event it is already enough logging. Direct writing to console is dirty and inconvenient to use with custom loggers, it may violate a format of application logs.

This is exactly what the default uncaught exception handler does minus exiting the process....

Contributor

petkaantonov commented Feb 13, 2015

The problem with doing nothing is that the feature might as well not exist as it will be undiscoverable to most developers.

IMHO emitting some event it is already enough logging. Direct writing to console is dirty and inconvenient to use with custom loggers, it may violate a format of application logs.

This is exactly what the default uncaught exception handler does minus exiting the process....

@tellnes

This comment has been minimized.

Show comment
Hide comment
@tellnes

tellnes Feb 13, 2015

Member

I would say emitting an event eg unhandledRejection and throw if there is no event handler. Similar to how uncaughtException works. Or maybe we could emit it as an uncaughtException if there is no handler for unhandledRejection.

At least, don't choose to do nothing. Errors which are silenced is a nightmare to debug.

Member

tellnes commented Feb 13, 2015

I would say emitting an event eg unhandledRejection and throw if there is no event handler. Similar to how uncaughtException works. Or maybe we could emit it as an uncaughtException if there is no handler for unhandledRejection.

At least, don't choose to do nothing. Errors which are silenced is a nightmare to debug.

@golyshevd

This comment has been minimized.

Show comment
Hide comment
@golyshevd

golyshevd Feb 13, 2015

@benjamingr

@golyshevd - just to be clear - are you for throwing or for no default?

I am for no default as emitting event as @petkaantonov already implemented. It might be simply changed to throw like

process.on('unhandledRejection', function (err) {
    throw err;
});

process.on('uncaughtException', function (err) {
   log(err);
});

@benjamingr

@golyshevd - just to be clear - are you for throwing or for no default?

I am for no default as emitting event as @petkaantonov already implemented. It might be simply changed to throw like

process.on('unhandledRejection', function (err) {
    throw err;
});

process.on('uncaughtException', function (err) {
   log(err);
});
@aheckmann

This comment has been minimized.

Show comment
Hide comment
@aheckmann

aheckmann Feb 13, 2015

Contributor

-1 for throwing bc not everyone agrees. we cannot break anyone's programs and this potentially would.

+1 for logging by default. as EventEmitter by default logs a message when the number of max listeners has been exceeded but supports overriding the default behavior through setMaxListeners etc, and as uncaughtException crashes the program by default yet supports overriding that behavior by setting a listener, we could log by default here and support overriding that behavior when a user sets an unhandledRejection listener.

+1 for documenting this at the top of the Promises page in iojs.com


Sent from Mailbox

On Fri, Feb 13, 2015 at 6:48 AM, Dmitry notifications@github.com wrote:

@benjamingr

@golyshevd - just to be clear - are you for throwing or for no default?
I am for no default as emitting event as @petkaantonov already implemented. It might be simply changed to throw like

process.on('unhandledRejection', function (err) {
    throw err;
});
process.on('uncaughtException', function (err) {
   log(err);
});

Reply to this email directly or view it on GitHub:
#830 (comment)

Contributor

aheckmann commented Feb 13, 2015

-1 for throwing bc not everyone agrees. we cannot break anyone's programs and this potentially would.

+1 for logging by default. as EventEmitter by default logs a message when the number of max listeners has been exceeded but supports overriding the default behavior through setMaxListeners etc, and as uncaughtException crashes the program by default yet supports overriding that behavior by setting a listener, we could log by default here and support overriding that behavior when a user sets an unhandledRejection listener.

+1 for documenting this at the top of the Promises page in iojs.com


Sent from Mailbox

On Fri, Feb 13, 2015 at 6:48 AM, Dmitry notifications@github.com wrote:

@benjamingr

@golyshevd - just to be clear - are you for throwing or for no default?
I am for no default as emitting event as @petkaantonov already implemented. It might be simply changed to throw like

process.on('unhandledRejection', function (err) {
    throw err;
});
process.on('uncaughtException', function (err) {
   log(err);
});

Reply to this email directly or view it on GitHub:
#830 (comment)

@meandmycode

This comment has been minimized.

Show comment
Hide comment
@meandmycode

meandmycode Feb 13, 2015

+1 for throwing personally, but perhaps I'm not understanding the current support problem. If (native) promises are new- how are people using them to any degree yet? surely this has zero impact on promise libraries. If you decide to upgrade to native promises then you deal with the behavior change. Swallowing errors by default? seems like an unbelievable discussion for such a low level platform.

+1 for throwing personally, but perhaps I'm not understanding the current support problem. If (native) promises are new- how are people using them to any degree yet? surely this has zero impact on promise libraries. If you decide to upgrade to native promises then you deal with the behavior change. Swallowing errors by default? seems like an unbelievable discussion for such a low level platform.

@Fishrock123 Fishrock123 added the discuss label Feb 13, 2015

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Feb 13, 2015

Member

To be clear to those thinking this is about swallowing errors "by default": not handling a promise error is the same as forgetting to check the err parameter passed to your callback, which of course we see happening all the time in the real world as well as in example code. The fact is that promises actually have a better story than callback-errors here, because since promises have two callbacks (one for fulfilled, one for rejected), we can detect if you didn't handle the error, and emit an event.

So promises swallow errors "by default" to the same extent callbacks do. This is about how we can be even better.

Member

domenic commented Feb 13, 2015

To be clear to those thinking this is about swallowing errors "by default": not handling a promise error is the same as forgetting to check the err parameter passed to your callback, which of course we see happening all the time in the real world as well as in example code. The fact is that promises actually have a better story than callback-errors here, because since promises have two callbacks (one for fulfilled, one for rejected), we can detect if you didn't handle the error, and emit an event.

So promises swallow errors "by default" to the same extent callbacks do. This is about how we can be even better.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Feb 13, 2015

Member

... and that actually leads to a good way of addressing the question posed in the OP.

Let's say you had a magic way of detecting if someone forgot to handle the err parameter to their callback, and instead just proceeded as normal---either on purpose or because of forgetfulness. Would you like io.js to use this magic power to log to the console when that happens? Crash your program? Do nothing?

The tradeoffs are very similar, so I hope your answer to that question can help you contextualize the question posed in the OP.

Member

domenic commented Feb 13, 2015

... and that actually leads to a good way of addressing the question posed in the OP.

Let's say you had a magic way of detecting if someone forgot to handle the err parameter to their callback, and instead just proceeded as normal---either on purpose or because of forgetfulness. Would you like io.js to use this magic power to log to the console when that happens? Crash your program? Do nothing?

The tradeoffs are very similar, so I hope your answer to that question can help you contextualize the question posed in the OP.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Feb 13, 2015

Member

Let's say you had a magic way of detecting if someone forgot to handle the err parameter to their callback

Forgot to handle the err parameter and an actual scenario occurred where the err parameter was not null (that is, an error happened).

I like the comparison by the way.

Member

benjamingr commented Feb 13, 2015

Let's say you had a magic way of detecting if someone forgot to handle the err parameter to their callback

Forgot to handle the err parameter and an actual scenario occurred where the err parameter was not null (that is, an error happened).

I like the comparison by the way.

@petkaantonov

This comment has been minimized.

Show comment
Hide comment
@petkaantonov

petkaantonov Feb 13, 2015

Contributor

not handling a promise error is the same as forgetting to check the err parameter passed to your callback

It's not the same at all, callback errors are always/generally operational while promise rejections can be either programmer errors or operational errors.

Contributor

petkaantonov commented Feb 13, 2015

not handling a promise error is the same as forgetting to check the err parameter passed to your callback

It's not the same at all, callback errors are always/generally operational while promise rejections can be either programmer errors or operational errors.

@meandmycode

This comment has been minimized.

Show comment
Hide comment
@meandmycode

meandmycode Feb 13, 2015

I disagree that unhandled promise rejection is like callback error, the error callback pattern makes it very hard to accidentally forget about the error happening, as its forced as part of callback flow, you either get what you wanted or an error, promises however break this apart (for the better), plus you are assuming that the callback pattern node has somehow proved the concept for how things should be. It works but its not an ideal.

Chrome is already dealing with this issue retroactively by now ensuring that unhandled rejections throw, the only reason the entire page doesn't get dismantled is because historically the default reaction to unhandled errors in browsers is to try and keep going.

I disagree that unhandled promise rejection is like callback error, the error callback pattern makes it very hard to accidentally forget about the error happening, as its forced as part of callback flow, you either get what you wanted or an error, promises however break this apart (for the better), plus you are assuming that the callback pattern node has somehow proved the concept for how things should be. It works but its not an ideal.

Chrome is already dealing with this issue retroactively by now ensuring that unhandled rejections throw, the only reason the entire page doesn't get dismantled is because historically the default reaction to unhandled errors in browsers is to try and keep going.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Feb 13, 2015

Member

Chrome is already dealing with this issue retroactively by now ensuring that unhandled rejections throw

This is inaccurate. Chrome logs; it does not throw.

Member

domenic commented Feb 13, 2015

Chrome is already dealing with this issue retroactively by now ensuring that unhandled rejections throw

This is inaccurate. Chrome logs; it does not throw.

@meandmycode

This comment has been minimized.

Show comment
Hide comment
@meandmycode

meandmycode Feb 13, 2015

Ah, apologies on that detail- this seemed to be the original idea suggested from what I had last seen, but you are obviously deeper down the rabbit hole :).

Was the decision not to throw/onerror in Chrome related to how people had already started to use promises? presumably by not always chaining them correctly, ie that you may return a promise that is chained in the 'next tick' instead of the current?

If so, out of interest; were there any scenarios that meant you couldn't chain in the current tick?

Ah, apologies on that detail- this seemed to be the original idea suggested from what I had last seen, but you are obviously deeper down the rabbit hole :).

Was the decision not to throw/onerror in Chrome related to how people had already started to use promises? presumably by not always chaining them correctly, ie that you may return a promise that is chained in the 'next tick' instead of the current?

If so, out of interest; were there any scenarios that meant you couldn't chain in the current tick?

@petkaantonov

This comment has been minimized.

Show comment
Hide comment
@petkaantonov

petkaantonov Feb 13, 2015

Contributor

If so, out of interest; were there any scenarios that meant you couldn't chain in the current tick?

If you have a container that creates a promise that is meant to be used later then you cannot chain in the current tick. However that container should just do promise.catch(() => ) so that errors are not surfaced until the promise is chained from outside the container.

In other cases you may accidentally write such code but it can be always refactored.

Contributor

petkaantonov commented Feb 13, 2015

If so, out of interest; were there any scenarios that meant you couldn't chain in the current tick?

If you have a container that creates a promise that is meant to be used later then you cannot chain in the current tick. However that container should just do promise.catch(() => ) so that errors are not surfaced until the promise is chained from outside the container.

In other cases you may accidentally write such code but it can be always refactored.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Feb 17, 2015

Member

@domenic can you link to the relevant parts of the streams spec that requires/needs attaching error handlers asynchronously?

Member

benjamingr commented Feb 17, 2015

@domenic can you link to the relevant parts of the streams spec that requires/needs attaching error handlers asynchronously?

@jonathanong

This comment has been minimized.

Show comment
Hide comment
@jonathanong

jonathanong Feb 18, 2015

Contributor

i believe these are serious programmer errors, so it should kill the process or be passed to uncaughtException unless you change the default behavior. i'm not a big fan of logging because if you rely on stdout or stderr, it's noisy.

Contributor

jonathanong commented Feb 18, 2015

i believe these are serious programmer errors, so it should kill the process or be passed to uncaughtException unless you change the default behavior. i'm not a big fan of logging because if you rely on stdout or stderr, it's noisy.

@petkaantonov

This comment has been minimized.

Show comment
Hide comment
@petkaantonov

petkaantonov Feb 18, 2015

Contributor

i'm not a big fan of logging because if you rely on stdout or stderr, it's noisy.

Again, this is exactly what the default uncaughtException does minus exiting the process. Are you seriously suggesting that the default should be exiting the process without writing the stack to stderr? As a side note if your stderr is noisy you should really fix the errors :D

Contributor

petkaantonov commented Feb 18, 2015

i'm not a big fan of logging because if you rely on stdout or stderr, it's noisy.

Again, this is exactly what the default uncaughtException does minus exiting the process. Are you seriously suggesting that the default should be exiting the process without writing the stack to stderr? As a side note if your stderr is noisy you should really fix the errors :D

@jonathanong

This comment has been minimized.

Show comment
Hide comment
@jonathanong

jonathanong Feb 18, 2015

Contributor

@petkaantonov i like my stderr silent. it's not noisy at all and i'd like to keep it that way :D

i guess a better way to pharse this is that i think these unhandled errors should be passed to uncaughtException, which (basically) console.error(err.stack); process.exit(1); unless there's a listener. in this specific instance, we can make it unhandledException to differentiate these errors.

i didn't explicitly say that it should console.error(err.stack); process.exit(1); but that's what i implied. not just process.exit(1) - that would be absolutely useless.

Contributor

jonathanong commented Feb 18, 2015

@petkaantonov i like my stderr silent. it's not noisy at all and i'd like to keep it that way :D

i guess a better way to pharse this is that i think these unhandled errors should be passed to uncaughtException, which (basically) console.error(err.stack); process.exit(1); unless there's a listener. in this specific instance, we can make it unhandledException to differentiate these errors.

i didn't explicitly say that it should console.error(err.stack); process.exit(1); but that's what i implied. not just process.exit(1) - that would be absolutely useless.

@Raynos

This comment has been minimized.

Show comment
Hide comment
@Raynos

Raynos Feb 27, 2015

Contributor

I think one of the very useful features of uncaught exceptions is the --abort-on-uncaught-exception flag.

Having an --abort-on-unhandled-rejection flag will satisfy the most diehard "crash first" fanatics (like myself).

This means I get the most important thing I want, which is a core dump with the actual stack of the exception.

Contributor

Raynos commented Feb 27, 2015

I think one of the very useful features of uncaught exceptions is the --abort-on-uncaught-exception flag.

Having an --abort-on-unhandled-rejection flag will satisfy the most diehard "crash first" fanatics (like myself).

This means I get the most important thing I want, which is a core dump with the actual stack of the exception.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Feb 27, 2015

Member

👍 for an --abort-on-unhandled-rejection flag. I like that idea.

Member

benjamingr commented Feb 27, 2015

👍 for an --abort-on-unhandled-rejection flag. I like that idea.

@misterdjules misterdjules referenced this issue in nodejs/node-v0.x-archive Mar 8, 2015

Closed

Consider exposing promise unhandled rejection hook #8997

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Mar 8, 2015

Member

I think we haven't really reached a consensus here - this has not generated enough interest given how new the feature is in my opinion.

Our options are:

  • Bring this up for a TC meeting which means that the TC decides on this default behaviour. Given how the discussion about the hooks themselves went it did not seem like multiple TC members had used the hooks or userland promises that provide similar hooks I do not think this is an optimal way to proceed.
  • Deciding not to have a default behaviour for a while - I think that giving it half a year and seeing how people use it will give us a lot of information, that said the window for introducing default behaviour is closing since it will start breaking peoples' code (assuming the behavior that will be decided on can break).

I think the most realistic option is to give this another few months and to document that default behavior will be introduced in the future. What do you think?

Member

benjamingr commented Mar 8, 2015

I think we haven't really reached a consensus here - this has not generated enough interest given how new the feature is in my opinion.

Our options are:

  • Bring this up for a TC meeting which means that the TC decides on this default behaviour. Given how the discussion about the hooks themselves went it did not seem like multiple TC members had used the hooks or userland promises that provide similar hooks I do not think this is an optimal way to proceed.
  • Deciding not to have a default behaviour for a while - I think that giving it half a year and seeing how people use it will give us a lot of information, that said the window for introducing default behaviour is closing since it will start breaking peoples' code (assuming the behavior that will be decided on can break).

I think the most realistic option is to give this another few months and to document that default behavior will be introduced in the future. What do you think?

@meandmycode

This comment has been minimized.

Show comment
Hide comment
@meandmycode

meandmycode Mar 8, 2015

My 2c here are that it may already be too late to add abortive behavior in, not only from io.js usage but how developers have been taught (or lack or teaching) from existing platforms, and the fact promises first landed in an environment that does not crash on unhandled exceptions has tainted the expectation (both of developers and its implementors).

The reality is that the concept of a maybe unhandled? rejection is bad, it exists to support bad/lazy programming but is also completely impossible to abstractly handle.. what am I going to do in this event callback realistically? who knows what this promise is, it may have come from 20 layers deep of dependent packages, developers will just end up having ON UNHANDLED -> IF DEBUG -> LOG

I will say that I would expect outreach to occur to stop third party libraries filling up event logs with pointless rejections, in which case you may as well have made the behavior correct anyway.

My 2c here are that it may already be too late to add abortive behavior in, not only from io.js usage but how developers have been taught (or lack or teaching) from existing platforms, and the fact promises first landed in an environment that does not crash on unhandled exceptions has tainted the expectation (both of developers and its implementors).

The reality is that the concept of a maybe unhandled? rejection is bad, it exists to support bad/lazy programming but is also completely impossible to abstractly handle.. what am I going to do in this event callback realistically? who knows what this promise is, it may have come from 20 layers deep of dependent packages, developers will just end up having ON UNHANDLED -> IF DEBUG -> LOG

I will say that I would expect outreach to occur to stop third party libraries filling up event logs with pointless rejections, in which case you may as well have made the behavior correct anyway.

@MicahZoltu

This comment has been minimized.

Show comment
Hide comment
@MicahZoltu

MicahZoltu May 26, 2015

export class MyData {
    constructor() {
        // prefetch to provide a better user experience
        this.resultPromise = http.get('http://www.google.com');
    }
}

export class ButtonClickHandler {
    onClick(myData) {
        // display spinner in case the promise is not yet fulfilled
        showSpinner();
        myData.resultPromise.then(result => {
            // display data to user
            showResult(result);
        }, error => {
            // display error to user
            showError(error);
        });
    }
}

In the above example, assume that MyData is constructed during application startup and the web request is kicked off right away, possibly before there is a reasonable UI to render errors to the user. At some point in the future, the user clicks some button and onClick is called. At that point in time, the promise is checked for its results.

I am of the opinion that this is a perfectly reasonable use case and generally a good practice. The promise is just an object that stores a future result or failure.

Many have related errors in promises to exceptions and I believe this is where the problem stems from. A promise is more akin to an if block than an exception. You are basically saying, "once this promise is fulfilled, run one of two code paths. You are not saying, "once this promise is fulfilled, run this code path or throw an exception." If you want the exception behavior then promises are not the right answer. You want some other construct that, while similar, does not keep rejected promises around.

export class MyData {
    constructor() {
        // prefetch to provide a better user experience
        this.resultPromise = http.get('http://www.google.com');
    }
}

export class ButtonClickHandler {
    onClick(myData) {
        // display spinner in case the promise is not yet fulfilled
        showSpinner();
        myData.resultPromise.then(result => {
            // display data to user
            showResult(result);
        }, error => {
            // display error to user
            showError(error);
        });
    }
}

In the above example, assume that MyData is constructed during application startup and the web request is kicked off right away, possibly before there is a reasonable UI to render errors to the user. At some point in the future, the user clicks some button and onClick is called. At that point in time, the promise is checked for its results.

I am of the opinion that this is a perfectly reasonable use case and generally a good practice. The promise is just an object that stores a future result or failure.

Many have related errors in promises to exceptions and I believe this is where the problem stems from. A promise is more akin to an if block than an exception. You are basically saying, "once this promise is fulfilled, run one of two code paths. You are not saying, "once this promise is fulfilled, run this code path or throw an exception." If you want the exception behavior then promises are not the right answer. You want some other construct that, while similar, does not keep rejected promises around.

@zloirock zloirock referenced this issue in zloirock/core-js May 26, 2015

Closed

Remove unhandled promise rejection code. #72

@vkurchatkin

This comment has been minimized.

Show comment
Hide comment
@vkurchatkin

vkurchatkin May 26, 2015

Member

@Zoltu this is a perfectly reasonable use case and it has been discussed a lot. You can suppress global rejection handler by attaching noop handler to your promise:

export class MyData {
    constructor() {
        // prefetch to provide a better user experience
        this.resultPromise = http.get('http://www.google.com');
        this.resultPromise.catch(() => {})
    }
}
Member

vkurchatkin commented May 26, 2015

@Zoltu this is a perfectly reasonable use case and it has been discussed a lot. You can suppress global rejection handler by attaching noop handler to your promise:

export class MyData {
    constructor() {
        // prefetch to provide a better user experience
        this.resultPromise = http.get('http://www.google.com');
        this.resultPromise.catch(() => {})
    }
}
@MicahZoltu

This comment has been minimized.

Show comment
Hide comment
@MicahZoltu

MicahZoltu May 26, 2015

Ah, great news. I have been looking around the internet at the various implementations of ES6 promises and so far io.js is the first that has provided a way to override this behavior. Great work!

Ah, great news. I have been looking around the internet at the various implementations of ES6 promises and so far io.js is the first that has provided a way to override this behavior. Great work!

@petkaantonov

This comment has been minimized.

Show comment
Hide comment
@petkaantonov

petkaantonov May 26, 2015

Contributor

Many have related errors in promises to exceptions and I believe this is where the problem stems from. A promise is more akin to an if block than an exception. You are basically saying, "once this promise is fulfilled, run one of two code paths. You are not saying, "once this promise is fulfilled, run this code path or throw an exception." If you want the exception behavior then promises are not the right answer. You want some other construct that, while similar, does not keep rejected promises around.

This is also very wrong, 50% of the entire point of promises is that errors in promises work the same as exceptions.

Contributor

petkaantonov commented May 26, 2015

Many have related errors in promises to exceptions and I believe this is where the problem stems from. A promise is more akin to an if block than an exception. You are basically saying, "once this promise is fulfilled, run one of two code paths. You are not saying, "once this promise is fulfilled, run this code path or throw an exception." If you want the exception behavior then promises are not the right answer. You want some other construct that, while similar, does not keep rejected promises around.

This is also very wrong, 50% of the entire point of promises is that errors in promises work the same as exceptions.

@MicahZoltu

This comment has been minimized.

Show comment
Hide comment
@MicahZoltu

MicahZoltu May 26, 2015

@petkaantonov Your sentiment seems to confirm that I have correctly identified the dividing line between the two camps.

I believe that async/await will better solve the problem you are trying to solve. You can write your aync code just like your sync code and have catch blocks and last chance exception handlers.

I have read the article you linked and I didn't get the same thing from it that you did.

With async/await not terribly far off, it seems prudent to let promises solve the problem of branching/chaining on success/error and await can solve the problem of anync exception handling. If promises are implemented with last chance handlers then we will end up with two solutions to the anync exception handling problem and nothing solving the other problem.

If anync were not in sight I would be more compelled to say that promises should provide last chance handlers and there is no option for the other problem, though it seems that there would be room for two different constructs.

@petkaantonov Your sentiment seems to confirm that I have correctly identified the dividing line between the two camps.

I believe that async/await will better solve the problem you are trying to solve. You can write your aync code just like your sync code and have catch blocks and last chance exception handlers.

I have read the article you linked and I didn't get the same thing from it that you did.

With async/await not terribly far off, it seems prudent to let promises solve the problem of branching/chaining on success/error and await can solve the problem of anync exception handling. If promises are implemented with last chance handlers then we will end up with two solutions to the anync exception handling problem and nothing solving the other problem.

If anync were not in sight I would be more compelled to say that promises should provide last chance handlers and there is no option for the other problem, though it seems that there would be room for two different constructs.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Jun 4, 2015

Member

It seems that we're forced to have no default behavior by stagnation.

Member

benjamingr commented Jun 4, 2015

It seems that we're forced to have no default behavior by stagnation.

@benjamingr benjamingr closed this Jun 4, 2015

@vkurchatkin

This comment has been minimized.

Show comment
Hide comment
@vkurchatkin

vkurchatkin Jun 4, 2015

Member

I don't think it's a good idea :-)

Member

vkurchatkin commented Jun 4, 2015

I don't think it's a good idea :-)

@vkurchatkin vkurchatkin reopened this Jun 4, 2015

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Jun 4, 2015

Member

@vkurchatkin ok - can you bring it up to TC agenda then :D ?

Member

benjamingr commented Jun 4, 2015

@vkurchatkin ok - can you bring it up to TC agenda then :D ?

@ivan-kleshnin

This comment has been minimized.

Show comment
Hide comment
@ivan-kleshnin

ivan-kleshnin Nov 12, 2015

Throwing an error is the sanest default option fo my taste.

I write a lot of utility scripts in JS and I'm deadly bored by

process.on("unhandledRejection", (reason, promise) => {
  throw reason;
});

mantra or equivalent import in every independent JS file. Some scripts can be written sync-only but as soon as you get http or sql where there is no sync equivalent, it's coming.

And, to be honest, provided arguments for Logging or Doing nothing do sound like a joke to me.
The only valid counter-argument is that damned backward compatibility.

Throwing an error is the sanest default option fo my taste.

I write a lot of utility scripts in JS and I'm deadly bored by

process.on("unhandledRejection", (reason, promise) => {
  throw reason;
});

mantra or equivalent import in every independent JS file. Some scripts can be written sync-only but as soon as you get http or sql where there is no sync equivalent, it's coming.

And, to be honest, provided arguments for Logging or Doing nothing do sound like a joke to me.
The only valid counter-argument is that damned backward compatibility.

@MicahZoltu

This comment has been minimized.

Show comment
Hide comment
@MicahZoltu

MicahZoltu Nov 12, 2015

@ivan-kleshnin when would the error be thrown and what would the stack trace look like?

@ivan-kleshnin when would the error be thrown and what would the stack trace look like?

@ivan-kleshnin

This comment has been minimized.

Show comment
Hide comment
@ivan-kleshnin

ivan-kleshnin Nov 12, 2015

@Zoltu not sure I'm getting you correct. Can you elaborate?
I'm ok with one more item in stack trace. I'm also ok with the possibility to cloak unhandled errors if its intentional i.e. explicit. Hiding errors by default smells like PHP and breaks my trust in platform.
Especially, when in "errors" we include "syntax errors" like @bminer already stated.

@Zoltu not sure I'm getting you correct. Can you elaborate?
I'm ok with one more item in stack trace. I'm also ok with the possibility to cloak unhandled errors if its intentional i.e. explicit. Hiding errors by default smells like PHP and breaks my trust in platform.
Especially, when in "errors" we include "syntax errors" like @bminer already stated.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Nov 12, 2015

Member

@Zoltu that's not what C# does at all. C# has a concept of an execution context - for example ASP.NET will fail the request even if there are no unhandled rejections just because there are async in flight actions created in the request context and not resolved by the time you're sending the response.

Aside from that:

I'm 100% with @mikeal here, I have another suggestion: we wait for the TC to finish the official promise hooks, wait to see what browsers implement and in a year we go with whatever behavior browsers have settled on by then since that will be de facto standard.

Member

benjamingr commented Nov 12, 2015

@Zoltu that's not what C# does at all. C# has a concept of an execution context - for example ASP.NET will fail the request even if there are no unhandled rejections just because there are async in flight actions created in the request context and not resolved by the time you're sending the response.

Aside from that:

I'm 100% with @mikeal here, I have another suggestion: we wait for the TC to finish the official promise hooks, wait to see what browsers implement and in a year we go with whatever behavior browsers have settled on by then since that will be de facto standard.

@bminer

This comment has been minimized.

Show comment
Hide comment
@bminer

bminer Nov 12, 2015

Contributor

wait to see what browsers implement and in a year we go with whatever behavior browsers have settled on by then since that will be de facto standard.

@benjamingr - I don't mind the idea, but Node can set standards, too. :) Plus, browsers don't "crash" on uncaught exceptions; they have some goofy onerror handler thing and dump errors to the console. Node is just a different beast in this regard. Thoughts?

All in favor of moving this discussion to unhandled-rejections-spec issue page, say "Aye."

Contributor

bminer commented Nov 12, 2015

wait to see what browsers implement and in a year we go with whatever behavior browsers have settled on by then since that will be de facto standard.

@benjamingr - I don't mind the idea, but Node can set standards, too. :) Plus, browsers don't "crash" on uncaught exceptions; they have some goofy onerror handler thing and dump errors to the console. Node is just a different beast in this regard. Thoughts?

All in favor of moving this discussion to unhandled-rejections-spec issue page, say "Aye."

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Nov 12, 2015

Member

@bminar that site and spec are dead, maybe @domenic would like to revive them but honestly I think he's busy doing a million other things and getting browsers to add the hooks first.

Also - node can set standards, but I think if users are used to the browsers logging to the console then similar behavior by default in node might be a good idea.

@paulirish did you guys reach anything meaningful regarding the instrumentation when you investigated the promises tab? Is the process leading to the choice of "log unhandled rejections to the console" documented somewhere?

Similarly - who implemented this in other browsers?

Member

benjamingr commented Nov 12, 2015

@bminar that site and spec are dead, maybe @domenic would like to revive them but honestly I think he's busy doing a million other things and getting browsers to add the hooks first.

Also - node can set standards, but I think if users are used to the browsers logging to the console then similar behavior by default in node might be a good idea.

@paulirish did you guys reach anything meaningful regarding the instrumentation when you investigated the promises tab? Is the process leading to the choice of "log unhandled rejections to the console" documented somewhere?

Similarly - who implemented this in other browsers?

@winterland1989

This comment has been minimized.

Show comment
Hide comment
@winterland1989

winterland1989 Nov 30, 2015

Just come across this issue, it reminds me a month ago i present a new continuation based solution other than promise in the ES mail list, and everyone is trying to convince me promise is the right way to do it, after some search i also found this compositional-functions proposal.

Now i suggest everyone give a closer look at this proposal and my implementation, which IMO solves the most problem of promise, since the async-await proposal is going make its way into ES7, and the gate of choosing async primitives are closing, please pay some attention and thanks!

Just come across this issue, it reminds me a month ago i present a new continuation based solution other than promise in the ES mail list, and everyone is trying to convince me promise is the right way to do it, after some search i also found this compositional-functions proposal.

Now i suggest everyone give a closer look at this proposal and my implementation, which IMO solves the most problem of promise, since the async-await proposal is going make its way into ES7, and the gate of choosing async primitives are closing, please pay some attention and thanks!

@LinusU

This comment has been minimized.

Show comment
Hide comment
@LinusU

LinusU Nov 30, 2015

Contributor

@winterland1989 Your change from cb(err, data) to cb(dataOrErr) makes it instantly incompatible with every other node module out-of-the-box. I even like reject, resolve better than that. Why did chose to do only one argument and then introduce instanceof Error checking?

Contributor

LinusU commented Nov 30, 2015

@winterland1989 Your change from cb(err, data) to cb(dataOrErr) makes it instantly incompatible with every other node module out-of-the-box. I even like reject, resolve better than that. Why did chose to do only one argument and then introduce instanceof Error checking?

@winterland1989

This comment has been minimized.

Show comment
Hide comment
@winterland1989

winterland1989 Nov 30, 2015

@LinusU Thanks for reading my implementation!

The reason why cb(dataOrErr) interface was chosen is simplifying error handling, and this design choice make the implementation of retry/race/parallel... much cleaner. And i admit it's somehow an arbitrary design choice.

Of course this is just an implementation detail of compositional-functions proposal, and we can open a discuss on the interface of final implementation if this really annoy a lot of people.

The bottom line is, since javascript is somehow a functional language, we don't really have to lock ourself with a state machine based solution(promise), which has obvious flaws, such as error handling and cancellation. Borrow some functional idea such as continuations and call-cc from other language doesn't hurt.

@LinusU Thanks for reading my implementation!

The reason why cb(dataOrErr) interface was chosen is simplifying error handling, and this design choice make the implementation of retry/race/parallel... much cleaner. And i admit it's somehow an arbitrary design choice.

Of course this is just an implementation detail of compositional-functions proposal, and we can open a discuss on the interface of final implementation if this really annoy a lot of people.

The bottom line is, since javascript is somehow a functional language, we don't really have to lock ourself with a state machine based solution(promise), which has obvious flaws, such as error handling and cancellation. Borrow some functional idea such as continuations and call-cc from other language doesn't hurt.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Jan 24, 2016

Member

I'm going to close this as we've been unable to progress with this discussion or with any proposal.

PRs and further discussion welcome.

Member

benjamingr commented Jan 24, 2016

I'm going to close this as we've been unable to progress with this discussion or with any proposal.

PRs and further discussion welcome.

@benjamingr benjamingr closed this Jan 24, 2016

@noinkling

This comment has been minimized.

Show comment
Hide comment
@noinkling

noinkling Feb 4, 2016

@Zoltu

In this scenario, they simply use async/await and likely never have to work directly with a promise. If they forget a catch block, they'll get an exception in the console.

Correct me if I'm wrong or if I'm speaking out of place, but that's incorrect isn't it? Use of await requires an enclosing async function (I noticed that this was missing from your examples), so if you forget to use try/catch it just becomes the rejection value for the promise that that function returns instead, and you're back to where you started - you have to catch it somewhere or else it will be swallowed, therefore async/await has the exact same issue.

I only bring this up because, as a newcomer to Node, I ran into this myself recently while using async/await through Babel. If I'm any indication of what's to come, there will be people staring at their log output for half an hour wondering why their incoming server request is failing silently and timing out in the browser. In fact I did actually have a try/catch block in my function, but the error was occurring in a (decidedly synchronous) JSON.stringify call that I had ignorantly left outside it (lesson learned). If the unhandled rejection had been logged by default (much like it is in Chrome) I would have presumably seen the word "unhandled" along with the error and instantly known what I did wrong. As it stands I will be adding process.on('unhandledRejection', err => { console.error('Unhandled promise rejection ', err.stack); }); or similar to every Node application I build because of how informative it will be if I or anyone else using my code messes up again.

@Zoltu

In this scenario, they simply use async/await and likely never have to work directly with a promise. If they forget a catch block, they'll get an exception in the console.

Correct me if I'm wrong or if I'm speaking out of place, but that's incorrect isn't it? Use of await requires an enclosing async function (I noticed that this was missing from your examples), so if you forget to use try/catch it just becomes the rejection value for the promise that that function returns instead, and you're back to where you started - you have to catch it somewhere or else it will be swallowed, therefore async/await has the exact same issue.

I only bring this up because, as a newcomer to Node, I ran into this myself recently while using async/await through Babel. If I'm any indication of what's to come, there will be people staring at their log output for half an hour wondering why their incoming server request is failing silently and timing out in the browser. In fact I did actually have a try/catch block in my function, but the error was occurring in a (decidedly synchronous) JSON.stringify call that I had ignorantly left outside it (lesson learned). If the unhandled rejection had been logged by default (much like it is in Chrome) I would have presumably seen the word "unhandled" along with the error and instantly known what I did wrong. As it stands I will be adding process.on('unhandledRejection', err => { console.error('Unhandled promise rejection ', err.stack); }); or similar to every Node application I build because of how informative it will be if I or anyone else using my code messes up again.

@MicahZoltu

This comment has been minimized.

Show comment
Hide comment
@MicahZoltu

MicahZoltu Feb 7, 2016

@noinkling Hmm, in C# you aren't allowed to use await inside of a function that isn't marked as async. I didn't realize that ES allowed you to have a non-async function with await used inside of it. I never really thought about it before, but I wonder if this sort of problem is why C# forces you to mark your methods with await as async?

@noinkling Hmm, in C# you aren't allowed to use await inside of a function that isn't marked as async. I didn't realize that ES allowed you to have a non-async function with await used inside of it. I never really thought about it before, but I wonder if this sort of problem is why C# forces you to mark your methods with await as async?

@noinkling

This comment has been minimized.

Show comment
Hide comment
@noinkling

noinkling Feb 7, 2016

@Zoltu You misunderstand me, I'm saying that ES does currently require an enclosing async function in order to use await (though as I understand it, top-level await is being considered). You left it out in the examples you gave. Therefore, since any await requires an enclosing async function (which returns a promise), with the current Node behavior errors will never be logged by default, they will be swallowed just like any other promise unless explicitly handled, even if they are 'transformed' temporarily to exceptions within the function.

In other words, async/await doesn't solve anything with respect to the logging problem, which makes sense, since it's based on promises under-the-hood.

@Zoltu You misunderstand me, I'm saying that ES does currently require an enclosing async function in order to use await (though as I understand it, top-level await is being considered). You left it out in the examples you gave. Therefore, since any await requires an enclosing async function (which returns a promise), with the current Node behavior errors will never be logged by default, they will be swallowed just like any other promise unless explicitly handled, even if they are 'transformed' temporarily to exceptions within the function.

In other words, async/await doesn't solve anything with respect to the logging problem, which makes sense, since it's based on promises under-the-hood.

@MicahZoltu

This comment has been minimized.

Show comment
Hide comment
@MicahZoltu

MicahZoltu Feb 8, 2016

Ah, you are right, I misunderstood you and I think you are correct, that async/await only helps if you are using it everywhere. If you are using await to interact with promises exclusively then you won't have any trouble and there shouldn't be any need for unhandled rejection handlers. If you are sometimes interacting with the promise directly (e.g., .then(...)) then you have to be more careful.

In my experience with C#, very few people interact with Tasks (promise equivalent) directly. They all interact with them via the await keyword. The only people who interact with Tasks directly are people who need more advanced control, which is the same set of people who would benefit from no unhandled rejection handler.

Ah, you are right, I misunderstood you and I think you are correct, that async/await only helps if you are using it everywhere. If you are using await to interact with promises exclusively then you won't have any trouble and there shouldn't be any need for unhandled rejection handlers. If you are sometimes interacting with the promise directly (e.g., .then(...)) then you have to be more careful.

In my experience with C#, very few people interact with Tasks (promise equivalent) directly. They all interact with them via the await keyword. The only people who interact with Tasks directly are people who need more advanced control, which is the same set of people who would benefit from no unhandled rejection handler.

@noinkling

This comment has been minimized.

Show comment
Hide comment
@noinkling

noinkling Feb 8, 2016

If you are using await to interact with promises exclusively then you won't have any trouble and there shouldn't be any need for unhandled rejection handlers. If you are sometimes interacting with the promise directly (e.g., .then(...)) then you have to be more careful.

There's no difference though (other than syntax) - either you use .catch with promises or try/catch with await, otherwise the error is silent. You have to handle it explicitly either way or nothing will be logged. To reiterate: async/await changes nothing in terms of logging behavior compared to promises, and therefore doesn't solve the issue of silent errors. In C# it might be different, I don't know.

If you are using await to interact with promises exclusively then you won't have any trouble and there shouldn't be any need for unhandled rejection handlers. If you are sometimes interacting with the promise directly (e.g., .then(...)) then you have to be more careful.

There's no difference though (other than syntax) - either you use .catch with promises or try/catch with await, otherwise the error is silent. You have to handle it explicitly either way or nothing will be logged. To reiterate: async/await changes nothing in terms of logging behavior compared to promises, and therefore doesn't solve the issue of silent errors. In C# it might be different, I don't know.

@MicahZoltu

This comment has been minimized.

Show comment
Hide comment
@MicahZoltu

MicahZoltu Feb 8, 2016

Perhaps I am misunderstanding something but as I understand it if you ALWAYS interact with promises via await then the only way you'll miss an exception is if you never looked at the result of a promise.

let fooPromise = getPromise(); // no throw here, we haven't tried to interact with the result yet
let barPromise = getPromise(); // will never throw, but since you never awaited the result it seems you don't really care
let foo = await fooPromise; // exception thrown here
actOnFoo(foo); // we'll either get here or we'll see an exception bubble up the stack
let fooPromise = getPromise();
let barPromise = getPromise();
fooPromise.then(result => {
    let foo = result;
    actOnFoo(foo); // we may never get here *AND* never see an exception, which is one reason why `await` is better
});

This practice depends on using async/await all the way up the call stack. Perhaps the issue you are referring to is the fact that at the very top (as you mentioned) you have to call your top level function. I agree, that Node should allow using await from top-level functions. In the meantime, I would recommend something like this:

async main() {
    // TODO: write application in here, use `await` everywhere interacting with promises
}

main().catch(error => console.log(error));

That being said, we are now getting into style questions and this style depends on not executing code in the global space (which seems to be very common in ES). C# has the same sort of problem, and you can see the same sort of answer here: http://stackoverflow.com/a/24601591/879046. Howover, since C# only has a single entry point which is a function, it isn't possible to accidentally execute code outside of the wrapped execution context so it is much harder to screw up than in Node.

Perhaps I am misunderstanding something but as I understand it if you ALWAYS interact with promises via await then the only way you'll miss an exception is if you never looked at the result of a promise.

let fooPromise = getPromise(); // no throw here, we haven't tried to interact with the result yet
let barPromise = getPromise(); // will never throw, but since you never awaited the result it seems you don't really care
let foo = await fooPromise; // exception thrown here
actOnFoo(foo); // we'll either get here or we'll see an exception bubble up the stack
let fooPromise = getPromise();
let barPromise = getPromise();
fooPromise.then(result => {
    let foo = result;
    actOnFoo(foo); // we may never get here *AND* never see an exception, which is one reason why `await` is better
});

This practice depends on using async/await all the way up the call stack. Perhaps the issue you are referring to is the fact that at the very top (as you mentioned) you have to call your top level function. I agree, that Node should allow using await from top-level functions. In the meantime, I would recommend something like this:

async main() {
    // TODO: write application in here, use `await` everywhere interacting with promises
}

main().catch(error => console.log(error));

That being said, we are now getting into style questions and this style depends on not executing code in the global space (which seems to be very common in ES). C# has the same sort of problem, and you can see the same sort of answer here: http://stackoverflow.com/a/24601591/879046. Howover, since C# only has a single entry point which is a function, it isn't possible to accidentally execute code outside of the wrapped execution context so it is much harder to screw up than in Node.

@noinkling

This comment has been minimized.

Show comment
Hide comment
@noinkling

noinkling Feb 8, 2016

Let me make a fairer comparison:

async function doAsyncStuff() {
  await promiseThatRejects();
}
doAsyncStuff().catch(error => console.log(error));

vs

function doAsyncStuff() {
  return promiseThatRejects();
}
doAsyncStuff().catch(error => console.log(error));

Anywhere that await can be used to propagate an error, returning the promise can be used to the same effect (which is typically how you work with promises in order to maintain a flat structure). Of course, the wrapper function in the 2nd example is unnecessary in this case, but it's needed to make a fair comparison and in real code would depend on context (due to interacting with other APIs or whatever). This might be more realistic:

async function doAsyncStuff() {
  try {
    await getPromise();
    // ...
  } catch (error) {
    console.log(error);
  }
}
doAsyncStuff();

vs

getPromise()
  .then(...)
  .catch(error => console.log(error));

But that's irrelevant to the issue at hand.

One of the main reasons for promises and async/await existing in the first place is to avoid a deeply-nested call stack, so error propagation up a stack shouldn't be much of a concern in the first place... but if for some reason it is, then async/await offers nothing that standard promises can't provide in this regard, and the fact remains that neither paradigm will do any logging without the error being explicitly caught and logged somewhere (yes, top-level counts).

If and when top-level await makes it into the language, then I think you might possibly have a point, depending on how errors at that level are handled. That's a big "if" and a big "when" though.

If you still disagree, feel free to PM me, I didn't mean to hijack the thread with this discussion.

Let me make a fairer comparison:

async function doAsyncStuff() {
  await promiseThatRejects();
}
doAsyncStuff().catch(error => console.log(error));

vs

function doAsyncStuff() {
  return promiseThatRejects();
}
doAsyncStuff().catch(error => console.log(error));

Anywhere that await can be used to propagate an error, returning the promise can be used to the same effect (which is typically how you work with promises in order to maintain a flat structure). Of course, the wrapper function in the 2nd example is unnecessary in this case, but it's needed to make a fair comparison and in real code would depend on context (due to interacting with other APIs or whatever). This might be more realistic:

async function doAsyncStuff() {
  try {
    await getPromise();
    // ...
  } catch (error) {
    console.log(error);
  }
}
doAsyncStuff();

vs

getPromise()
  .then(...)
  .catch(error => console.log(error));

But that's irrelevant to the issue at hand.

One of the main reasons for promises and async/await existing in the first place is to avoid a deeply-nested call stack, so error propagation up a stack shouldn't be much of a concern in the first place... but if for some reason it is, then async/await offers nothing that standard promises can't provide in this regard, and the fact remains that neither paradigm will do any logging without the error being explicitly caught and logged somewhere (yes, top-level counts).

If and when top-level await makes it into the language, then I think you might possibly have a point, depending on how errors at that level are handled. That's a big "if" and a big "when" though.

If you still disagree, feel free to PM me, I didn't mean to hijack the thread with this discussion.

@vkarpov15

This comment has been minimized.

Show comment
Hide comment
@vkarpov15

vkarpov15 Oct 19, 2016

I'm not gonna mince words, the "PromiseRejectionHandledWarning" default is crap DX. Much of the point of promises is that you can handle promise rejections asynchronously, now a bunch of perfectly valid promise patterns print spammy warnings unless you tweak the default.

before(function() {
  sinon.stub(obj.asyncFunction).returns(Promise.reject(err));
});

it('my test', function (done) {
  obj.asyncFunction.catch(error => {
    assert.ok(error);
    done();
  });
});
let lastPromise;

stream.on('data', () => {
  if (lastPromise) {
    lastPromise = lastPromise.then(() => doSomethingAsync(), () => doSomethingAsync());
  } else {
    lastPromise = doSomethingAsync();
  }
});

Etc. This warning is tangentially useful for debugging, but you need to remember to shut it off in prod and if you're doing anything even remotely non-trivial with promises.

I'm not gonna mince words, the "PromiseRejectionHandledWarning" default is crap DX. Much of the point of promises is that you can handle promise rejections asynchronously, now a bunch of perfectly valid promise patterns print spammy warnings unless you tweak the default.

before(function() {
  sinon.stub(obj.asyncFunction).returns(Promise.reject(err));
});

it('my test', function (done) {
  obj.asyncFunction.catch(error => {
    assert.ok(error);
    done();
  });
});
let lastPromise;

stream.on('data', () => {
  if (lastPromise) {
    lastPromise = lastPromise.then(() => doSomethingAsync(), () => doSomethingAsync());
  } else {
    lastPromise = doSomethingAsync();
  }
});

Etc. This warning is tangentially useful for debugging, but you need to remember to shut it off in prod and if you're doing anything even remotely non-trivial with promises.

@Fishrock123

This comment has been minimized.

Show comment
Hide comment
@Fishrock123

Fishrock123 Oct 19, 2016

Member

but you need to remember to shut it off in prod and if you're doing anything even remotely non-trivial with promises.

This would be correct, but being the default makes it usable by people who may not have as good of a hand on their potential problems.

Member

Fishrock123 commented Oct 19, 2016

but you need to remember to shut it off in prod and if you're doing anything even remotely non-trivial with promises.

This would be correct, but being the default makes it usable by people who may not have as good of a hand on their potential problems.

@vkarpov15

This comment has been minimized.

Show comment
Hide comment
@vkarpov15

vkarpov15 Oct 19, 2016

Not necessarily, just makes perfectly valid patterns look scarier than they are and makes npm modules that do perfectly reasonable things print warnings upstream, making it so end users have to worry about module behavior. It isn't node's job to fix promise hell, that's what async/await or co/yield is for. Then, when people start blogging about async/await hell, you can come up with another half-baked "kid wheels mode" hack like domains for callbacks or logging warnings to the console for promises :)

Not necessarily, just makes perfectly valid patterns look scarier than they are and makes npm modules that do perfectly reasonable things print warnings upstream, making it so end users have to worry about module behavior. It isn't node's job to fix promise hell, that's what async/await or co/yield is for. Then, when people start blogging about async/await hell, you can come up with another half-baked "kid wheels mode" hack like domains for callbacks or logging warnings to the console for promises :)

@bminer

This comment has been minimized.

Show comment
Hide comment
@bminer

bminer Oct 19, 2016

Contributor

I still think that the default (yet customizable) behavior for unhandled rejections is to crash the app, just like an uncaught exception.

If you want to handle a Promise rejection after it occurs, you can bind your own "unhandledRejection" handler to do just that. But, we cannot just ignore unhandled Promise rejections by default. Your custom "unhandledRejection" handler could do something like start a 30 second timer to see if the Promise is eventually handled. But, eventually, all Promise rejections should be handled... if not, it's probably a bug, perhaps even a bug that should intentionally crash the app.

Unhandled rejections are potentially just as dangerous as uncaught exceptions IMHO. The app could be in an undefined state. Even SyntaxErrors can result in unhandled rejections, for example.

Contributor

bminer commented Oct 19, 2016

I still think that the default (yet customizable) behavior for unhandled rejections is to crash the app, just like an uncaught exception.

If you want to handle a Promise rejection after it occurs, you can bind your own "unhandledRejection" handler to do just that. But, we cannot just ignore unhandled Promise rejections by default. Your custom "unhandledRejection" handler could do something like start a 30 second timer to see if the Promise is eventually handled. But, eventually, all Promise rejections should be handled... if not, it's probably a bug, perhaps even a bug that should intentionally crash the app.

Unhandled rejections are potentially just as dangerous as uncaught exceptions IMHO. The app could be in an undefined state. Even SyntaxErrors can result in unhandled rejections, for example.

@vkarpov15

This comment has been minimized.

Show comment
Hide comment
@vkarpov15

vkarpov15 Oct 19, 2016

So should every node module have its own special snowflake handler for on('unhandledRejection')? What if some module wants the behavior to be "crash the app" and another module wants it to be "ignore it"? Global behavior is global, no way for child libs to change it without risk of conflict. The point of this sort of feature is for the end user app to decide how it wants to handle unhandled rejections, not for node to decide what promise patterns should node modules be allowed to use, which is what it does now.

So should every node module have its own special snowflake handler for on('unhandledRejection')? What if some module wants the behavior to be "crash the app" and another module wants it to be "ignore it"? Global behavior is global, no way for child libs to change it without risk of conflict. The point of this sort of feature is for the end user app to decide how it wants to handle unhandled rejections, not for node to decide what promise patterns should node modules be allowed to use, which is what it does now.

@bminer

This comment has been minimized.

Show comment
Hide comment
@bminer

bminer Oct 20, 2016

Contributor

@vkarpov15 - To answer your question: Yes. IMHO, late binding a rejection handler is poor practice -- libs should avoid this pattern. If they really want to do it, they can have their own "unhandledRejection" handler.

Maybe Promises should have a flag to indicate that late binding is desired at the time of construction? Also, separate discussion but maybe Promises should have a .cancel() method to abort the async operation?

Contributor

bminer commented Oct 20, 2016

@vkarpov15 - To answer your question: Yes. IMHO, late binding a rejection handler is poor practice -- libs should avoid this pattern. If they really want to do it, they can have their own "unhandledRejection" handler.

Maybe Promises should have a flag to indicate that late binding is desired at the time of construction? Also, separate discussion but maybe Promises should have a .cancel() method to abort the async operation?

@vkarpov15

This comment has been minimized.

Show comment
Hide comment
@vkarpov15

vkarpov15 Oct 20, 2016

It's far from bad practice, it's necessary practice for more advanced use cases, which is what modules should be providing wrappers around anyway. Correct me if I'm wrong, but the 'unhandledRejection' handler is global to the process as a whole, which means a module that wants to crash on unhandled rejections will screw over every other module. TBH I understand your point, if you'd asked me in early 2015 I would've made a similar argument, "bad practice don't do it". But getting more comfortable with promises and spending time in observables land has made me realize that restricting promises to "let me just chain a bunch of HTTP / database calls together" is really a narrow view of what promises can do, which is what this default limits you to.

It's far from bad practice, it's necessary practice for more advanced use cases, which is what modules should be providing wrappers around anyway. Correct me if I'm wrong, but the 'unhandledRejection' handler is global to the process as a whole, which means a module that wants to crash on unhandled rejections will screw over every other module. TBH I understand your point, if you'd asked me in early 2015 I would've made a similar argument, "bad practice don't do it". But getting more comfortable with promises and spending time in observables land has made me realize that restricting promises to "let me just chain a bunch of HTTP / database calls together" is really a narrow view of what promises can do, which is what this default limits you to.

@benjamingr

This comment has been minimized.

Show comment
Hide comment
@benjamingr

benjamingr Oct 20, 2016

Member

This discussion has been had several times and the consensus is to warn on it now and throw on GC when we have the hooks to do so.

It is easy to attach an empty catch handler which will suppress the warning.

There are at least 5 threads like this one - I recommend you look at the NodeJS/promises one if you want more up to date arguments.

Member

benjamingr commented Oct 20, 2016

This discussion has been had several times and the consensus is to warn on it now and throw on GC when we have the hooks to do so.

It is easy to attach an empty catch handler which will suppress the warning.

There are at least 5 threads like this one - I recommend you look at the NodeJS/promises one if you want more up to date arguments.

@bminer

This comment has been minimized.

Show comment
Hide comment
@bminer

bminer Dec 1, 2016

Contributor

Referencing a relevant PR here: #8217
Process warnings will occur starting in Node 7

Contributor

bminer commented Dec 1, 2016

Referencing a relevant PR here: #8217
Process warnings will occur starting in Node 7

@rsp

This comment has been minimized.

Show comment
Hide comment
@rsp

rsp Dec 2, 2016

Contributor

I wrote the caught module to simplify working with use cases described by @MicahZoltu and @vkarpov15 without globally changing the behavior of everything by listening to the events.

It was originally written as an example for this answer on Stack Overflow: Should I refrain from handling Promise rejection asynchronously? but maybe it could be useful to someone who reads this discussion.

Contributor

rsp commented Dec 2, 2016

I wrote the caught module to simplify working with use cases described by @MicahZoltu and @vkarpov15 without globally changing the behavior of everything by listening to the events.

It was originally written as an example for this answer on Stack Overflow: Should I refrain from handling Promise rejection asynchronously? but maybe it could be useful to someone who reads this discussion.

@icysailor icysailor referenced this issue in promises-aplus/unhandled-rejections-spec Dec 5, 2016

Open

Deferred rejection #9

@justjavac justjavac referenced this issue in justjavac/the-front-end-knowledge-you-may-not-know Jul 6, 2017

Open

unhandledrejection 处理没有显式捕获的 Promise 异常 #7

@busterc busterc referenced this issue in busterc/promise-do-whilst Dec 19, 2017

Closed

Thrown error is silenced when rejection is not caught #3

openui5bot pushed a commit to SAP/openui5 that referenced this issue Feb 7, 2018

[INTERNAL][FIX] ui5loader: avoid 'Uncaught (in promise)' log entries
To support a mixture of sync and async module loading, the ui5loader
creates a Promise for any requested resource to track its loading state,
even when the first requestor uses a synchronous API.

As long as no asynchronous API asks for the same resource, this promise
is never used. Modern browsers report a rejection of such a promise as
'Uncaught (in promise)' error and report it on the console.

One solution would be to create the Promise only on demand, but then
error tracking would be more complicated. As an alternative, this change
adds a dummy catch handler to the promise. This has no impact on error
reporting via APIs (errback - covered by tests).

For some background, see the discussion about rejected promises ( 
nodejs/node#830 ) and the current answer of
the HTML5 spec to the topic (
https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections
)

Change-Id: I99e5c1384a4e5caf7ccdf21db1dfadbdc75884f3

openui5bot pushed a commit to SAP/openui5 that referenced this issue Mar 26, 2018

[INTERNAL][FIX] ui5loader: avoid 'Uncaught (in promise)' log entries
To support a mixture of sync and async module loading, the ui5loader
creates a Promise for any requested resource to track its loading state,
even when the first requestor uses a synchronous API.

As long as no asynchronous API asks for the same resource, this promise
is never used. Modern browsers report a rejection of such a promise as
'Uncaught (in promise)' error and report it on the console.

One solution would be to create the Promise only on demand, but then
error tracking would be more complicated. As an alternative, this change
adds a dummy catch handler to the promise. This has no impact on error
reporting via APIs (errback - covered by tests).

For some background, see the discussion about rejected promises ( 
nodejs/node#830 ) and the current answer of
the HTML5 spec to the topic (
https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections
)

Change-Id: I99e5c1384a4e5caf7ccdf21db1dfadbdc75884f3
CR-Id: 002075125900000687202018
(cherry picked from commit 47ac957)
BCP: 1880209531
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment