Promise exception reported after random delay in Firefox - javascript

While doing some work I saw "uncaught exception" errors reported in Firebug appearing at seemingly random moments. The exceptions were thrown inside promises in background.
At first I was surprised the errors were reported at all, as my understanding was that errors thrown in promises are simply caught and passed along the callbacks chain. But they are reported in post-Opera (=Chrome) too, and they don't kill the script, so that is a good thing then.
But in Opera exceptions are reported immediately while in Firefox there is some seemingly random delay (from a couple of seconds to half a minute). Why is that?
Here is a test code:
var p = new Promise( okLater );
p.then( kill );
function okLater( pass, fail ) {
setTimeout( pass.bind( this, "O.K." ), 10 );
}
function kill() {
console.log( "Killing" );
throw "Oops"
}

At first I was surprised the errors were reported at all, as my understanding was that errors thrown in promises are simply caught and passed along the callbacks chain
Yes, they are indeed. That promise you have created via p.then(kill) is going to be rejected.
But we don't really want that. Exceptions that are silently ignored? Not a good idea. So we need unhandled rejection handling. An "unhandled exception" is a rejected promise that got no error handler attached. There is a bit of problem with identifying these, because the error handler could be attached later intentionally, and we wouldn't want that promise to be reported. Still, promise implementations have the capability to track their promises and report "possibly unhandled rejections", see this answer and How do I handle exceptions globally with native promises in node.js? for further details.
reported in Firebug appearing at seemingly random moments.
The moment you can be totally sure that a promise was rejected without a handler being attached is when it is garbage-collected. IIRC, Firefox has indeed implemented this hook, so these "random moments" would depend on the GC behaviour.

Related

Will Javascript ES6 promise support 'done' api?

For example
p = new Promise(function (resolve, reject) {
throw 'err';
});
p.done();
In most promise polyfill libs, the done will throw an error, and the current execution will exit.
But if we use p.then(), nothing will happen. The error is swallowed by the promise. If we use p.catch, we have no way to exit current execution. I want to achieve something like:
try {
// something
} catch (err) {
if (check(err)) {
throw err;
}
}
No.
Not only will .done likely not be supported in the future versions of the spec - it is unneeded. Quoting from the threads Mariusz linked to:
Domenic:
it's still error-prone: if you slip up and don't follow the rule even once, you might silence an error forever.
Mark Miller (who pioneered the concept of promises):
Note that weak-refs, hopefully in ES7, will provide us one of the diagnostic tools we need to bridge this gap. Using weak-refs, if a rejected promise gets collected without having notified any handlers, we can arrange that this generates a diagnostic. The promise implementation would have to keep the reason in the promise's executor (post-mortem gc handler), so that it has the diagnostic to report after discovery that the promise has been rejected.
Yehuda Kats on RSVP's error handler:
The approach we're taking in RSVP is to install an unhandled promise monitor that throws by default.
You can opt a particular promise out of this behavior by attaching a noop failure handler, if you know that you will be attaching asynchronous error handlers. We will probably have sugar for this (.undone :p)
In our experience, moving the burden from literally everyone to people who may want to attach async error handlers is appropriate.
And, from the actual repo that preceded the spec, Domenic said:
done's job will be done by integrating unhandled rejection tracking functionality into dev tools. Most TC39ers, from what I understand, as well as myself, perceive that as enough for the spec to be complete.
The spec committee did not just ignore .done, they deemed it was unnecessary and error prone. New modern promise libraries automatically detect unhandled rejections - two examples of this are When promises and Bluebird promises that pioneered the idea.
.done is an artifact - originating from the fact the browser could not detect unhandled rejections. Truth is - detecting them deterministically is impossible but for the vast majority of cases it is completely possible.
Don't believe me? Open Firefox and play with its native promises:
p = new Promise(function (resolve, reject) {
throw 'err';
});
// Logs as error: Unhandled error: `err`
Simply put - firefox uses garbage collection hooks in order to determine promises were disposed in an unhandled state and fires a global error handler which defaults to writing on the screen.
Now, the problem is native promises are not very usable yet - since in IE they don't exist and in Chrome unhandled rejection detection was not yet implemented - but it's coming and it'll be there. Meanwhile you can use an ES6 compatible library like Bluebird which will do this rejection tracking for you.
If you want to polyfill done (which I strongly recommend against) - the polyfill by torazaburo has a few shortcomings. It declares an enumerable property on the promise prototype and generally this is not how the spec was designed - you are expected to subclass promises in order to extend them rather than monkey patch them - sadly no implementations currently support this.
So in short:
Wait for native promises to stabilize before you use them - in the meanwhile you can use libraries that implement the spec like Bluebird. When it stabilizes not having .done will not be an issue at all.
Utilize patterns for detecting errors - for example check out the disposer pattern here.
Use the developer tools when available, long stack traces and and async debugging are big plusses. Also note you should not throw strings if you want meaningful stack traces.
Good luck and happy coding.
No, AFAIK done is not part of the spec. To mimic its behavior, you should throw the exception on the next tick, outside the purview of the promises chain:
p.catch(function(e) {
setTimeout(function() { throw e; });
});
This is essentially how libraries implement done. See excerpt from Q documents:
Much like then, but ... the resulting rejection reason is thrown as an exception in a future turn of the event loop.
Implementing done yourself
If you want to implement the approximate semantics of done as typically understood, then something like:
Promise.prototype.done = function(onFulfilled, onRejected) {
this
.then(onFulfilled, onRejected)
.catch(function(e) {
setTimeout(function() { throw e; });
})
;
};
Setting up an error handler
If you want a chance to handle these errors yourself, you could set up an error handler:
Promise.onError = function(e) {
console.log("The sky is falling", e);
throw e;
};
Then invoke the handler on the next tick:
Promise.prototype.done = function(onFulfilled, onRejected) {
this
.then(onFulfilled, onRejected)
.catch(function(e) {
setTimeout(Promise.onError || function() { throw e; }, 1, e);
})
;
};
Current statement of TC39 is that this issue can be and should be solved natively in browser engines with developer tools. That's why they're opposite providing done within native API.
It's indeed controversial decision, see following links for discussions on that matter:
https://github.com/domenic/promises-unwrapping/issues/19
http://mozilla.6506.n7.nabble.com/Where-d-Promise-done-go-td281461.html
https://github.com/promises-aplus/promises-spec/issues/43
https://github.com/slightlyoff/Promises/issues/33

Should a Promise.reject message be wrapped in Error?

Using the native (ES6) Promise. Should I reject with an Error:
Promise.reject(new Error('Something went wrong'));
Or should I just reject with a string:
Promise.reject('Something went wrong');
And what is the difference in browser behaviour?
Yes, it most definitely should. A string is not an error, when you have errors usually it means something went wrong which means you'd really enjoy a good stack trace. No error - no stack trace.
Just like with try/catch, if you add .catch to a thrown rejection, you want to be able to log the stack trace, throwing strings ruins that for you.
I'm on mobile so this answer is rather short but I really can't emphasize enough how important this is. In large (10K+ LoC) apps stack traces in rejections really made the difference between easy remote bug hunting and a long night in the office.
I'm recommending to use Error object only (not a string) for sending the reasons.
Justification
Other parts of code are generating the Errors inside the Promise rejection reason...
If some code fails the exception returns the Error object. Also if you will call any external library, which does not support the Promise, it will throw the Error object when something fails.
If one of errors mentioned above occurs inside the Promise, it will be transformed into catch with Error object.
Therefore if you will use the string as promise rejection reason, you have to expect that the catch can occurs with your string (part of your code) or Error (when some general error occurs). So you will have to use ugly code (err.message || err) everywhere, when you have to handle the error.

Errors in a promise's error handler go missing

If I throw an error in a promise's catch handler, the error is never bubbled up!
Promise.resolve()
.then(function() { return foo.bar(); })
.then(function() { return console.log('ok!'); })
.catch(function(err) { return baz.quux(); });
In both Node v0.8 with the promise module and Chrome 36's JavaScript console, this prints nothing. I'm expecting to see ReferenceError: baz is not defined appear somewhere.
Shouldn't we see something? Is this a part of the promises spec that I missed?
UPDATE: Clarification: this happens in Chrome 36's V8 without any third-party module.
Yes, this is a problem promise implementations face. However the two you've chosen fail at it pretty bad. Indeed - the error is swallowed in your case and indeed you will not get any indication of this silent failure unless you attach a error handler yourself.
Your options are:
Use a library that offers .done like Q and manually append .done to every single promise in your code to indicate you will not attach handlers to it.
Use a library like Bluebird or When that does unhandled rejection detection.
In particular, Bluebird is faster than native promises and does this properly. It is also a superset of native promises so you can write code that uses that subset if you'd like (although it has a much richer API). The code you have above logs an unhandled rejection with Bluebird.
By the way, Firefox handles this much better and Firefox native promises detect unhandled rejections.

Under what circumstances might bluebird's "Possibly unhandled Error" warning be wrong?

The word "possibly" suggests there are some circumstances where you can get this warning in the console even if you catch the error yourself.
What are those circumstances?
This is pretty well explained in the docs:
Unhandled rejections/exceptions don't really have a good agreed-on
asynchronous correspondence. The problem is that it is impossible to
predict the future and know if a rejected promise will eventually be
handled.
The [approach that bluebird takes to solve this problem], is to call a
registered handler if a rejection is unhandled by the start of a
second turn. The default handler is to write the stack trace to
stderr or console.error in browsers. This is close to what happens
with synchronous code - your code doesn't work as expected and you
open console and see a stack trace. Nice.
Of course this is not perfect, if your code for some reason needs to
swoop in and attach error handler to some promise after the promise
has been hanging around a while then you will see annoying messages.
So, for example, this might warn of an unhandled error even though it will get handled pretty well:
var prom = Promise.reject("error");
setTimeout(function() {
prom.catch(function(err) {
console.log(err, "got handled");
});
}, 500);

Q- a resolved promise not calling the next task, Safari 6.1 + and 7+

We have a class as part of a large framework, and we use Q as our promise library.
We have a function similar to what you see below.
this._getData() returns a promise, and all of the other functions in the 'thens' receive and return a value properly.
Everything works most of the time.
Sometimes in Safari 6.1 + and 7+ (mainly when the data is not yet in the cache, i.e. the first time anyone ever requests it), the first promise (this._getData()) is created and fulfilled (I created a local log to ensure this), but none of the 'thens' are called. Q does not pass the fulfillment value to the next task.
There was a bug that looked similar to it in Q v0.8 which was fixed in v0.9, but we upgraded to v0.9 and it has not resolved the issue.
Here's the code:
someAsyncFunc: function(){
var promise = this._getData()
.then(this._doSomething_1)
.then(this._doSomething_2)
.then(...)
.then(...)
.then(this._doSomething_n)
.fail(this._onFail);
return promise;
}
It's interesting to note that once the bug happens, Q is completely broken. It may have been broken before this request due to something else that failed which uses Q, but we don't see any exceptions.
By completely broken, I mean that normally on any page which has Q (and when the bug does not occur), if you type this into the console:
Q(1).then(function(){console.log('then has occurred');});
then you will see the console log.
However, when the bug occurs, this does not happen.
Even trying:
var promise = Q(1);
promise.then(function(){console.log('then has occurred');});
//nothing happens
promise.isFulfilled() //true
Which seems to show that Q is just stuck in some weird state where it believes it is flushing when it's really not, so it never goes to the next task.
This is actually exactly what we're experiencing, as the this._getData() creates a new promise, performs an ajax call to the server and passes the deferred object to resolve into the callbacks, and successfully resolves the promise - but none of the next functions are called.
Would love to know if anyone encountered this and knows how to fix it, or has an idea how to continue debugging this.
Thanks
Update: After further debugging, it definitely looks like Q gets stuck flushing on a task, and is therefore never able to move to the next task.

Categories

Resources