- From: Tab Atkins Jr. <jackalmage@gmail.com>
- Date: Fri, 26 Apr 2013 13:30:49 -0700
- To: Juan Ignacio Dopazo <dopazo.juan@gmail.com>
- Cc: Kevin Smith <zenparsing@gmail.com>, Brendan Eich <brendan@mozilla.com>, "Mark S. Miller" <erights@google.com>, Douglas Crockford <douglas@crockford.com>, "public-script-coord@w3.org" <public-script-coord@w3.org>, Norbert Lindenberg <w3@norbertlindenberg.com>, Markus Lanthaler <markus.lanthaler@gmx.net>, EcmaScript <es-discuss@mozilla.org>
On Fri, Apr 26, 2013 at 11:44 AM, Juan Ignacio Dopazo <dopazo.juan@gmail.com> wrote: > 2013/4/26 Tab Atkins Jr. <jackalmage@gmail.com> >> >> On Fri, Apr 26, 2013 at 11:25 AM, Kevin Smith <zenparsing@gmail.com> >> wrote: >> > Actually, I may have gotten it terribly wrong (apologies). In my >> > prototype >> > implementation, the following: >> > >> > Future.accept(Future.resolve(1)).then(value => { >> > >> > console.log(value !== 1); >> > return Future.accept(Future.resolve(1)); >> > >> > }).then(value => { >> > >> > console.log(value === 1); >> > }); >> > >> > logs >> > >> > - true >> > - true >> > >> > Is that what it should be doing, according to the DOM spec? Anne, Alex? >> >> No, it should be "true", then "false". >> > I think Kevin's assertion is correct. According to the spec, callbacks are > wrapped in something called a "future wrapper callback". When a promise is > returned from the callback, the wrapper does this: > >> Let value be the result of invoking callback with argument as argument. >> (...) run resolver's resolve with value and the synchronous flag set. > > > resolve tries to adopt the promise by being recursive, effectively > flattening the promise: >> >> If value is a JavaScript Object, set then to the result of calling the >> JavaScript [[Get]] internal method of value with property name then. >> If JavaScript IsCallable(then) is true [treats all thenables the same >> way], run these substeps and then terminate these steps: >> Call the JavaScript [[Call]] internal method of then with this value value >> and resolve and reject as arguments. > > If resolved called the thenable's then() with accept and reject, it would > only unwrap one layer. Gah, you're right. So, this is a problem. The current behavior is incoherent wrt types, and is *inconsistent* between its first and second callbacks. First, incoherency. Given a Future<a>, its .then() method accepts two callbacks, each of which is supposed to have the signature "a -> Future<b>". We apply some magic to this so that you can also use functions of type "a -> b" (where "b" is not a Future) and we auto-massage it into working correctly. So far so good - this lets us chain Futures, which is great. Now, if you omit one of the callbacks, we need to supply a "default" behavior. This needs to match the signatures above. In the Futures spec, both Future.accept() and Future.reject() have the signatures "a -> Future<a>", which is exactly what we need. Future.resolve(), on the other hand, has the signature "(a or Future<a>) -> Future<a>", which is inconsistent with the above. Signatures like this can be *useful*, so that calling sites can pass in both plain values and Futures, but it doesn't match the required signature for the .then() callbacks, and so it's unacceptable for a default behavior. Second, inconsistency. As I explained above, the signature of Future.reject() is "a -> Future<a>". The signature of Future.resolve() is "(a or Future<a>) -> Future<a>". The fact that these two signatures are different means that omitting a callback will have *observably different behavior* based on which one you omitted. For example, in the case of "Future.accept(Future.accept(5)).then().then(alert)", it will alert "5", because the resolve semantics for the missing callback in the first .then() will perform extra unwrapping of the value. (In other words, it gives you a *different result* than just doing "Future.accept(Future.accept(5)).then(alert)", which will alert "<object Future>" or whatever.) On the other hand, in the case of "Future.reject(Future.reject(5)).then().then(null, alert), it will alert "<object Future>". (And you'll get the same behavior if you remove the intermediary ".then()".) This sort of inconsistency is *bad news* for authors trying to use Futures, as it means that the exact path the value took through accept/reject callbacks will change the value that it gets. ~TJ
Received on Friday, 26 April 2013 20:31:36 UTC