Code Monkey home page Code Monkey logo

unhandled-rejections-spec's People

Contributors

briancavalier avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unhandled-rejections-spec's Issues

Deferred rejection

Hi folks. Interested in feedback on some experimental promises with deferred rejection. Has this been considered as a solution?

The pattern is you create promises with rejection deferred: if they're rejected without a reject handler it will be postponed for later delivery. If a reject handler is added later the rejection is delivered. At the point when you expect your process to have handled the promise, you end the deferral which delivers any still-pending rejections as unhandled.

This makes it the responsibility of the programmer to specify when a rejection is considered unhandled. If you have a use for asynchronously-attached handlers you can do that without the engine getting in the way and without breaking other code by changing global behavior. But you don't completely lose error reporting because you still deliver anything unexpected at the end. You create little pockets of time where asynchronous attachment is valid.

There's a working implementation here. Usage and implementation are simple. It subclasses native Promises:
https://github.com/icysailor/promise-deferred-rejection

const { PromiseDeferredRejection } = require('./promise-deferred-rejection');
let resolve, reject;
const promise = new PromiseDeferredRejection((res,rej)=>{
  resolve = res;
  reject = rej;
});
reject('Rejected!'); // No unhandled rejection
setTimeout(()=>{
  promise.catch(reason=>{
    console.log('Handling rejection with reason: '+reason);
  });
},5000); // Rejection handled 5 seconds later
setTimeout(()=>{
  promise.release();
},8000); // Always end deferral in case normal process fails

@MicahZoltu @vkarpov15 Would this work for the advanced use cases you guys have mentioned?

Relevant to the discussion in #6 nodejs/node#830 nodejs/promises#26 https://gist.github.com/benjamingr/0237932cee84712951a2

Track and allow printing of unhandled rejections

The idea here would be, whenever an unhandled rejection occurs, add it to some array (or perhaps WeakMap). If it gets handled later, remove it from that array.

Then, allow printing of the unhandled rejection list. Either add is as a property to the library, or to a global environment (cf #2), or print it automatically on program exit/window onload, or print it periodically and automatically in debug mode, or...

This seems unlikely to solve the problem by itself. It's not a great experience to debug for hours before thinking to check the magic unhandled rejection list. Tooling could help, but then we're back at #2.

Background information

This is rough and not spec-worthy, but is meant to give us a common starting point for future issues.

Terminology

  • "Unhandled rejection": the occurrence wherein a promise transitions to the rejected state, but no rejection handlers are registered for it.
  • "Handled later": if a promise is in the rejection state with no rejection handlers, but then one is added to the promise (via a then or catch call), we say that the rejection has now been "handled later." Notably, this could happen several seconds later.
  • "Crash": the behavior of a program when a synchronous exception is not handled. In Node.js and Windows 8 apps this is an actual crash; in browsers, this is an error to the console. "Crash" is just a shorthand.

Statement of the issue

  • With sync exceptions, if they get to the top of the stack with no catch blocks, then you can be sure that nobody will ever handle them, and the runtime knows your program should crash.
  • With async rejections, if they are unhandled, crashing is not the correct behavior, because they could be handled later.
  • The reason for this is that promises are first-class objects that can be passed around for handling in e.g. other program contexts or in response to other asynchronous events. But disallowing unhandled rejections (by crashing the moment they appear) essentially prohibits the use of promises in the rejected state as first-class objects.
  • But, since we can't react to unhandled rejections immediately by crashing, how should we react to them? And, if they are handled later, how should that impact the steps we took when they first appeared?

Sample Code

var promise = pendingPromise();

promise.then(function () {
    console.log("I only attached a handler for fulfillment");
});

rejectPromise(promise, new Error("who handles me?"));
// Nobody sees the error! Oh no, maybe we should crash here?

// But if we crashed there, then how would this code ever get run?
setTimeout(function () {
    promise.then(undefined, function (err) {
        console.error("I got it!", err);
    });
}, 5000);

Library hooks

Like #2, but the hooks are put on the libraries themselves (e.g. Q.onUnhandledRejection = ... or Q.on("unhandledRejection", ...)).

This has the con of making it hard to write generic debugging tools, but the pro of not co-opting some environment feature (like console or the global object) for nefarious purposes.

`done`

The solution employed by Q and WinJS is a function called done on each promise, which is much like then except it signals that if there is an unhandled rejection for that promise, the program should crash. That is:

var f = fulfilledPromise();
var r = rejectedPromise(new Error("foo!"));

var noop = function () { };
var thrower = function () { throw new Error("bar!"); }

r.done(); // crashes with "foo!"
r.done(undefined, function () { }); // doesn't crash
r.done(undefined, thrower); // crashes with "bar!"

f.done(); // doesn't crash
f.done(thrower); // crashes with "bar!"

Notably, p.done(f, r) is equivalent to p.then(f, r).done(), so you can choose either style as convenient; the latter is a bit nicer for refactoring, but the former signals intent nicely in other situations.

Also, done returns undefined.

The guidance is that when creating a promise (either directly or via then), you should always either (a) return that promise so that the caller takes care of it, or (b) call .done() on it.

For more info on this see the MSDN article: http://msdn.microsoft.com/en-us/library/windows/apps/hh700337.aspx


Notably, this idea is composable with other ideas: if you "cap" a promise with .done(), that handles the rejection immediately with a sort of "default behavior" (i.e. crashing). So the other ideas can still be implementing to catch cases where you forget to cap.

In other words, this idea reduces the number of unhandled rejections present in a program, and with inhuman programmer diligence brings that number down to zero. But in cases where that number is not brought down to zero, other ideas still apply.

Fail-fast always

Fail fast all the time, but have a method to explicitly state that you intend to handle a rejection in a future turn of the event loop.

You could perhaps only throw if a rejection is not handled for say 1 second.

Lazy promises (making `done` compulsory)

Ass suggested by @Raynos in #2 we could make promises lazy until .done is called. Consider the promise(executor) syntax being proposed in promises-aplus/constructor-spec#3

The executor is a function which initiates the asynchronous operation. It has the potential to generate either a resolved promise or a rejected promise. If it's not called, then no rejected promise can exist, so we don't have an issue. So the key idea being presented here is to only call it when we know the promise's rejection will be handled.

By only calling the executor once .done is called, you can guarantee that .done will always be called for all promises. If .done is not called, the entire function will fail to do anything, which will be easy to quickly pick up when developing.

Having done that, it's a simple case of having .done behave as in #5 and crash the application appropriately by throwing the exception in the next turn of the event loop.

Transition

Step 1, ad .done to the promises spec.
Step 2, make sure all promise libraries call .done if present when assimilating promises.
Step 3, wait a month or so for this to be true of most major promise libraries
Step 4, begin using console.warn to warn people that they need to begin calling .done when .done is not called after a period of a few seconds.
Step 5, Implement this feature properly by making the promises lazy.

Environment hooks

This idea involves establishing a set of standard "environment hooks" which, if present, are called upon an unhandled rejection and upon it being handled later.

The essential idea would be something like two globally-known methods, e.g. console.unhandledRejection, console.rejectionHandled, which implementations call upon those two events---if they exist (and are callable). Presumably the former would give out a handle which the latter then accepts.

By itself this does nothing, but if people build tools that tap into these (e.g. browser extensions), they could provide a very nice debugging experience.

Fail-fast mode

Simply crash immediately upon unhandled rejections.

This would never be a default mode; if it were, the library would not be compliant with Promises/A+.

However, it might be a useful mode for debugging, especially for those systems wherein promises are not treated as first-class objects and passed around between asynchronous constructs.

This has the flaw that eventually your system will grow to use promises as first-class constructs, at which point either you'll have to turn this mode off permanently, or eliminate that use of promises and instead mangle your code to work differently.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.