promises-aplus / unhandled-rejections-spec Goto Github PK
View Code? Open in Web Editor NEWDiscussion and drafts of possible ways to deal with unhandled rejections, across implementations
Discussion and drafts of possible ways to deal with unhandled rejections, across implementations
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
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.
This is rough and not spec-worthy, but is meant to give us a common starting point for future issues.
then
or catch
call), we say that the rejection has now been "handled later." Notably, this could happen several seconds later.catch
blocks, then you can be sure that nobody will ever handle them, and the runtime knows your program should crash.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);
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.
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 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.
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.
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.
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.