W3C home > Mailing lists > Public > www-dom@w3.org > July to September 2013

RE: [promise] writing detailed spec of ProgressPromise & implementing it

From: Domenic Denicola <domenic@domenicdenicola.com>
Date: Thu, 8 Aug 2013 17:05:51 +0000
To: Jonathan Watt <jwatt@jwatt.org>
CC: "www-dom@w3.org" <www-dom@w3.org>
Message-ID: <B4AE8F4E86E26C47AC407D49872F6F9F878A9C5A@BY2PRD0510MB354.namprd05.prod.outlook.com>
Jonathan, glad to hear you're aware of and interesting in mitigating the issues. I agree that the issues you identified are some of the biggest problems. The other that comes to mind immediately is error handling: what happens when something synchronously throws inside of a progress handler? There have been no great answers to this question in Promises/A+ space. (However, we have been attempting to answer it in the context of both the browser and Node.js, where failing to catch an error crashes the process. If the solution needs to work only in browsers, maybe uncatchably delivering the error to `window.onerror` is acceptable.)

But even aside from these biggest problems, there are a number of others that come to mind:

- The impact of progress on existing APIs. For example, if you have two progress promises and do a `Promise.every` (or `Q.all`) on them, do you try to represent the progress of the composite promise? Do you start creating combinators specifically for progress promises? How do these combine with regular combinators, or regular promises? A combinatorial explosion is lurking in the background here.

- The conceptual issue with putting progress on the same footing as resolution. You're not putting it in `then`, which is good. (Although, `then`'s symmetry with the promise constructor is already broken by the WHATWG's choice of `new Promise(({ resolve, reject }) => ...)` instead of `new Promise((resolve, reject) => ...)`.) But if you put it in the resolver object, alongside `resolve` and `reject`, you must recognize that this is a very confusing place for it. Signaling progress has nothing to do with resolving the promise, unlike every other method on that object. And the responsibility of giving someone a resolver is very different from giving them the ability to notify about progress, so conflating them is problematic.

- You end up creating a bunch of meta-protocols around what the progress objects look like. In the end every progress-promise user ends up with something different---{ index, total }, bytesSoFar, { stepsDone, stepsTotal }, percent, fraction etc.---so it is not actually useful to have unified them under a single progress-promise interface, since such progress-promises cannot be handled generally.

- If you completely decouple progress from promise propagation, this is a very user-hostile experience. You get a progress-promise for an XHR, and you're in good shape. But then you realize that you're always parsing that XHR as JSON, so instead of writing `doXhr(url).then(...)`, you write

   function doJsonXhr(...args) {
       return doXhr(...args).then(JSON.parse);


   But `doJsonXhr` now returns a `Promise`, not a `ProgressPromise`, so you've lost information just by doing one of the most fundamental promise transforms!

   So if progress is decoupled from promise propagation, then it's questionable whether it deserves to be part of the promise interface at all. What does it have to do with the promise? They are both facets of the same async operation, but unrelated facets: one represents the actual operation itself, and the other is a side-channel of *meta*-data *about* the operation.

As such, I would suggest representing it as such a side-channel. In particular, for your directory-picker UI, I'd suggest an object such as `{ openSystemDirectoryPicker, addEventListener }`, or if you think that's not ergonomic, then have `openSystemDirectoryPicker` return an object that implements both `Promise` and `EventTarget` interfaces. A special progress-promise, that departs from standard `EventTarget` mechanisms and presumably entangles itself into the resolver etc., causes the problems outlined above.

Finally I'd like to reiterate that progress-promises are an evolutionary dead-end, in terms of the language. In the glorious ES6 future, we'll all be doing

Q.async(function *() {
   console.log(yield doXHR());

and in the glorious ES7 future, we'll all be doing

console.log(await doXHR());

(or something like it). There's no room in there for a progress metadata channel, and burdening promises with one hurts the transition from promises-as-awkard-objects to promises-as-shallow-coroutine-channels or promises-as-proxies-for-remote-objects.

Received on Thursday, 8 August 2013 17:07:36 UTC

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 22:37:03 UTC