- From: Sean Hogan <shogun70@westnet.com.au>
- Date: Mon, 20 May 2013 19:24:43 +1000
- To: "Tab Atkins Jr." <jackalmage@gmail.com>
- CC: "www-dom@w3.org" <www-dom@w3.org>, "public-script-coord@w3.org" <public-script-coord@w3.org>, Anne van Kesteren <annevk@annevk.nl>
On 20/05/13 12:05 PM, Tab Atkins Jr. wrote: > On Sun, May 19, 2013 at 6:04 PM, Sean Hogan <shogun70@westnet.com.au> wrote: >> On 20/05/13 8:32 AM, Tab Atkins Jr. wrote: >>> I *really* don't like this proposal. Comments inline, as usual. >>> >>> On Sat, May 18, 2013 at 3:09 PM, Sean Hogan <shogun70@westnet.com.au> >>> wrote: >>>> BENEFITS: >>>> >>>> The main benefits of this addition are: >>>> >>>> a. consistency in the way futures are initialized and resolved - that is, >>>> resolving is always asynchronous via `resolver.accept|reject`. >>> This comes at the expense of easier and simpler code to write in the >>> common case, when you're receiving an already-built future and are >>> just acting on it via .then(). You can no longer just return a value >>> like a normal function; you are forced to call `this.accept()`. >> You can still do this - I haven't proposed changing the behavior of .then(). > I'm saying that .thenfu() is less convenient *in general*, and the one > case where it is more convenient (when you want to hand out the > ability to resolve your chained future) is too small for the code > savings your proposal grants it. The fact that you can still use the > more convenient method doesn't change that. The accept/reject callbacks in .thenfu() have the same form as the `init` callback in `new Future()`. Do you think that form of `init` is not useful? >>> Given that you have to reject the promise based on uncaught errors in >>> the callback anyway (or else you lose an important quality of >>> promises), `this.reject()` doesn't give you anything >>> (`this.reject(val)` is identical to `throw val`, but longer). >>> >>> You've also removed the ability to do `this.resolve()`, for some >>> reason. This isn't explained at all in your proposal, which makes me >>> assume that you don't understand what it's used for. This means you >>> can't force your promise to delegate to another promise, as you can do >>> today. >> The resolve algorithm is still part of the way .then() handles callbacks - I >> haven't suggested changing that. > By making it only part of how .then() handles callbacks, you've > removed the ability of the FutureResolverCallback to use it. All the > essential primitives need to be exposed directly, not hidden in the > behavior of another function. So people who want future.then() will want auto-`then`-detection in their init callback and, therefore, resolver.resolve(). Fair enough. >>> Finally, you're overriding `this` to be the future's resolver, thus >>> ruling out any possibility of passing in class methods that want to >>> refer to the class instance when processing the future's value. It's >>> unclear what the effect of this would be on hard-bound methods, as >>> well. >> Yes, I couldn't work out why the callback would want access to the >> resolver's future. >> Could you give an example of that usage? > Apologies, I must have been unclear. I was referring to something like this: > > var x = new Foo(); > var f = getFuture(); > f.then(x.doSomething.bind(x)); Let's see. If I'm reading the spec right, what `f.then(x.doSomething.bind(x))` does is create a new Future (call it `newf`) and append to it a "future-callback-wrapper for the associated resolver (call it `newr`) and the callback (call it `onaccept`)". The wrapper should do more-or-less: function(arg) { try { var value = onaccept.call(newf, arg); newr.resolve(value); } catch(err) { newr.reject(err); } } Now `onaccept` is `x.doSomething.bind(x)` which is more-or-less: function() { var func = x.doSomething, thisArg = x; return func.apply(thisArg, arguments); } In this scenario `onaccept.call(newf, arg)` does the same as `onaccept(arg)` - `doSomething()` is never bound to anything other than `x`. So I can't see how it matters what `this` is set for `onaccept`. FWIW, Alex Russell's polyfill - https://github.com/slightlyoff/DOMFuture/blob/master/polyfill/src/Future.js - sets the `onaccept` callback's `this` to `null`. > Where Foo#doSomething relies on other properties or methods of the Foo > instance. If you override `this` for the callback, then the method > will break. If you *don't* override `this` (which is more likely > here, since `this` is hard-bound), then the callback can't resolve the > chained future. > See above >> Yes, the difference is precisely the creation of a new Future. Every time. > Every time you're further deferring resolution to another async > operation, yes. This doesn't seem to be onerous. > >> I addressed this in my proposal but I'll say it a different way here: >> >> As you say: >> .thenfu() can be implemented with .then() plus the creation of *another* >> new Future >> >> The reverse is also possible and even simpler: >> .then() can be implemented with .thenfu() plus the "resolve" algorithm >> >> So .thenfu is simpler internally and is simpler for mixing with non-Promise >> async code. > This is not a significant simplicity difference for either side, imo. > >> .then() is simpler for synchronous code and Promise objects, but... >> >>>> c. `then` doesn't need to be the feature-test for Promise objects, >>>> and doesn't need to be a forbidden method of non-Promise objects. >>> I don't understand how this is a result of your proposal. >> >> >> A contrived example: >> >> var Data = { >> x: 1, >> y: 2, >> then: function() { } >> } >> >> Future.accept() >> .then(function() { >> return Data; >> }) >> .done(function(value) { >> someExternalCodeExpectingData(value); >> }); >> >> This chain will stall (silently) because `Data` will be treated as a Promise >> object. >> Replace the .then() call with .thenfu and the following won't stall: >> >> .thenfu(function(value) { >> this.accept(Data); >> }) > Yes, this is why I don't understand. You can do the same thing in > Futures, once Anne fixes the spec like he's saying he will, by doing: > > .then(function(val) { > return Future.accept(Data); > } But that is just a restatement of: This can be addressed by creating a new Future. >> Personally: >> - I want my async code to look like async code; > I'm not sure what this means, but your examples aren't substantially > different in your proposal and the current spec. Where they do have > differences, I find the current spec simpler and easier. > >> - I don't like the idea of receiving some quantum "value-or-a-Promise"; > I don't understand. When do you ever receive a "value-or-a-Promise"? > >> - and if my code receives one then I want **my** code to be in charge of >> detecting it. > You can detect whether something is a Future or not easily - just do > an instanceof check. > >> I doubt I'm the only one who thinks this way. >> >> My proposal: >> - suits the async coding pattern, > I presume by this you mean Node's async pattern? No. I can't see how you could reasonably interpret "async" that way - .thenfu() callbacks match `new Future()` init callbacks. >> - adds two methods but **no** complexity to the spec, and > Every method is complexity. It's more things to implement and test, > it's more things for authors to remember and possibly get wrong. Even > literal function aliases have to justify themselves. But your solutions add more complexity than .thenfu(): a. "just create another new Future" - complexity for the JS dev b. "we could create a convenience wrapper" - complexity for the spec and browsers >> - to my knowledge doesn't compromise the current compatibility with JS >> Promise implementations. > Saying "don't use the Promise properties, they're busted" isn't great > compatibility, and it leaves us stranded in a halfway-land where we're > neither compatible with promises nor Node's async pattern. I don't > think it can justify itself on its merits sufficiently to overcome > these difficulties. That a big misrepresentation. I'm not suggesting removing .then() nor changing its behavior. And I haven't advocated Node's async pattern. Sean
Received on Monday, 20 May 2013 09:25:20 UTC