Promises

Domenic Denicola domenic at domenicdenicola.com
Wed Nov 7 07:50:43 PST 2012


From: es-discuss-bounces at mozilla.org [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Kevin Smith
Sent: Wednesday, November 7, 2012 09:58

> The only hard part that isn't really addressed by currently library implementations is error handling.  I feel pretty strongly that rejections (and by extension, errors thrown from `then` callbacks), should ALWAYS be observable.  In other words, if a rejection is not observable via error listeners at the time when error listeners should be called, then an unhandled error should be thrown by the runtime.

> In my usage of promises I have never wanted anything other than this behavior.

The problem with this is that it disallows treating promises as first-class objects that can be passed around, unobserved, only to be observed at a later date. As a trivial example, consider:

```
let promise = Q.reject(); // a rejected promise

setTimeout(=> {
    promise.then(null, (err) => {
        console.err("Got an error!", err);
    });
}, 100);
```

In this example there is nobody observing the promise at the time it is rejected, or even a tick after rejection. So should the rejection be thrown by the runtime? You would suggest yes. But then the error handling code inside the `setTimeout` will never be called.

---

OK, so that's a trivial example. What about a less trivial example? Well, consider using promises as remote objects. A rejected promise could be passed across the wire in various ways, all of which take much longer than a single tick. Or consider just normal control flow that uses promises as first-class mechanisms of state. For example the upthread-mentioned promises in place of loading events: many libraries will only end up listening to loading events far after they are completed, since the loading promise is a first-class observable property of the object being loaded (page, image, database, etc.).

In short, it creates a serious refactoring hazard. If you accept a promise, you can no longer introduce asynchronicity into your functions that handle it (ironic!) due to the risk of its errors escaping you:

```
function processData(promiseForDatabase) {
    // All good:
    return promiseForDatabase.then(=> ..., => ...)
}

function processData(promiseForDatabase) {
    // No good!! You lost the state of `promiseForDatabase`. Its errors have escaped,
    // possibly crashing your app if you are e.g. in a Node.js or Windows 8 Metro environment.
    return getDataForPreprocessing().then(=> {
        return promiseForDatabase.then(=> ..., => ...);
    });
}
```

---

As mentioned upthread, this is solved by task.js, but this is by far the thorniest issue faced by promise implementations today. In Q and WinJS, the solution is to always "cap" your promise chains with `.done()`. So all promise code should either be returning the promise, or capping with `.done()`. Other mechanisms we are considering are mostly about enabling greater visibility into any currently-unhandled rejections. For example, some type of Q.onunhandled/Q.onhandled pair, or integration into some simple browser-console extensions that would create a pane where you could view such rejected promises, or a mode that sets a maximum timeout before we consider an unhandled rejection erroneous and throw it (for development purposes), or somehow showing the errors on "exit" (page unload, process exit in Node, ...).


More information about the es-discuss mailing list