Tuesday, August 26, 2014

Debugging promises in Chrome

With the upcoming Chrome 36, Javascript Promises as specified in the upcoming revision of the ES6 standard are going to be activated by default as part of V8. Right now, you can already try it in the Dev or Canary release channels.
While a very useful addition to the language, especially with regard to asynchronous programming, Promises are peculiar when it comes to debugging: it swallows exceptions by default.

Consider the following code:
var p, q;
p = new Promise(function f(resolve, reject) { resolve(); });
try {
  q = p.then(function g(value) { throw "exception"; });
} catch (e) {
When p is constructed, the closure f passed to the Promise-constructor is run, with the result that the Promise is resolved at once. So in due time, the chained closure g is run, which throws. Counter-intuitively, this exception is not caught by the try-catch around it.
Upon more reflection, this seems reasonable: while g has been passed to p.chain within the try-catch, it's not actually run there. It's actually run in the scheduler that handles resolved promises. So where did the exception go?
If we run, at some later point, the following code:
q.catch(function h(value) { console.log(value); });
We will see that the logged value is precisely the exception thrown by g. The Promise object q was holding on to it all along!
This is a dilemma for developers though. If the reject handler is not carefully added to each Promise, there is the risk of missing exceptions.
Devtools to the rescue! As long as devtools is opened, exceptions thrown in Promises that, at the time of the throw, do not have any reject handler yet, will be considered an "uncaught exception". By toggling the "pause on uncaught exceptions" button in devtools, execution halts if we hit an exception that is swallowed by a Promise.

No comments:

Post a Comment