Re: Deprecating Future's .then()

On Mon, May 20, 2013 at 2:24 AM, Sean Hogan <shogun70@westnet.com.au> wrote:
> The accept/reject callbacks in .thenfu() have the same form as the `init`
> callback in `new Future()`.

No it doesn't, unless you've changed the proposal since your OP.  The
callback passed to the constructor receives a resolver as its
argument.  Your proposal is to set `this` in a thenfu callback to a
resolver object for the chained future.

> Do you think that form of `init` is not useful?

I think it's less convenient, but you have to expose a resolver
directly *somewhere*, and doing so in the constructor works well.  I
don't think we should aim for making then callbacks the same as the
resolver callback.

> So people who want future.then() will want auto-`then`-detection in their
> init callback and, therefore, resolver.resolve().
> Fair enough.

The resolve algorithm isn't about then-detection, it's about
conditional assimilation - if you give it a Future (or maybe a
thenable), it'll return a new Future linked to the input that
completes at the same time and with the same value (in other words, it
just passes it through, but does a copy as well); if you give it a
plain value, it returns a new Future that immediately accepts with
that value.

In other words, it conveniently converts a Future<value>-or-value into
a Future<value>, so you can operate on both cases with a single code
path.

>>>> 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`.

You're not understanding the problem I'm trying to point out.

Your suggested model offers, as the *only* way to resolve a future, a
resolver object bound to the `this` arg in the future callback.  I
gave an example where the `this` arg is already hard-bound to another
value, and *needs* to be hard-bound to that value to operate
correctly.

In your proposal, then, it becomes impossible to actually resolve the
future with that callback; you try to pass the resolver in via `this`,
but it gets blocked.

The problem is you trying to abuse the `this` arg as a data channel.
It already has an established purpose, which can clash with your
attempted usage.

> FWIW, Alex Russell's polyfill -
> https://github.com/slightlyoff/DOMFuture/blob/master/polyfill/src/Future.js
> - sets the `onaccept` callback's `this` to `null`.

In other words, the polyfill *doesn't do anything to the `this`
argument*.  It doesn't use it in any way, and if you pass in a
hard-bound function it will have no effect on it.  (Given the way
callbacks work, soft-binding is irrelevant - functions are either
unbound with `this` pointing to the global, or hard-bound with
.bind().  Setting `this` to null is safe for the unbound case, because
you shouldn't be depending on that global behavior anyway.)

>>> 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.

Yes.  In particular, though, it's showing that "create a new Future"
isn't an ordeal.  The actual code is virtually identical, and VMs can
optimize out the Future creation once it starts becoming common enough
to be worth it.

>>> 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.

It doesn't.  If you don't mean Node's async pattern, then I don't
understand what you mean, or why you wrote and referred to examples
that explicitly  use Node's async pattern.

>>> - 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

In the case where you need a resolver function directly, yes, which is
only necessary when using async functions that don't interoperate with
Futures.

In the more common case where you're only using sync code (and thus
can return an answer directly) or are using Future-friendly async code
(which will become more common as time goes on), the current spec is
simpler.

> b. "we could create a convenience wrapper" - complexity for the spec and
> browsers

Yup, but again, only for that case, and that only brings us up to the
same complexity as your suggestion, which adds two new functions.

>>> - 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.

That's exactly what you said, isn't it?  I don't know how else to
interpret "deprecate .then() and .catch()".

~TJ

Received on Monday, 20 May 2013 20:54:26 UTC