A Challenge Problem for Promise Designers (was: Re: Futures)

David Sheets kosmo.zb at gmail.com
Fri Apr 26 08:45:38 PDT 2013


On Fri, Apr 26, 2013 at 4:03 PM, Domenic Denicola
<domenic at domenicdenicola.com> wrote:
> From: David Sheets [kosmo.zb at gmail.com]
>
>> Why is there a semantic distinction between my thenables and your thenables?
>
> Because your thenables are not to be trusted! They could do pathological things like jQuery, or conceptually incoherent things like thenables-for-thenables. Sanitation at the boundary is the idea behind the resolve algorithm.

But I am the programmer! If my own tools distrust me, all hope is
lost. With the present special casing of own-promises, there can only
ever be a single semantics (own-promises) even if I have an
alternative semantics that I would like to use.

>> If someone is using nested thenables, presumably they have a good reason. Promises/A+ acknowledges this possibility by allowing own-promises to nest.
>
> Yes, but more importantly, it preserves the guarantees within a single library---whether they be allowing promises-for-thenables, or disallowing them.

It destroys the guarantee that foreign libraries are in charge of
their own resolution. Preserving foreign libraries' guarantees would
require meddling with their values the *least* (1-level assimilation).

> Q, when, RSVP, and others guarantee no promises-for-thenables. That is a great feature for consumers of those libraries, as has been emphasized many times in this thread (especially elegantly, I think, by David Bruant).

That's a great feature for those programmers! If Q, when, and RSVP
make that guarantee, they should enforce it by recursively resolving
in *their* bind (or assimilation) operation, not relying on
Promises/A+ or DOM Future to enforce it for them.

> If there were no recursive foreign thenable assimilation, then promises-for-thenables could sneak into Q/when/RSVP promise systems, breaking the assumptions of consumers of those promises.

After their recursively resolving bind? How?

If they have a thenable and use their libraries' bind, their invariant
is maintained.

>> If we are interesting in constructing the "most standard" promises system, surely this system must grant other, foreign systems the same possibility of nesting own-promises without interference?
>
> No. Generally, foreign systems *must* be normalized, for security concerns if nothing else.

What, specifically, are these security concerns?

> Trying to accommodate foreign system semantics into your own promise system is a recipe for disaster. Mark can expand upon this more in detail, if you think it's an important point.

It depends on what invariants are maintained, I believe. I've read
quite a number of threads about this topic (including far too many
pages of GitHub issues) and I've not, yet, come across something that
indicates that delegating resolution to each own implementation breaks
things.

>> Could you point me to some code that needs dynamic flattening?
>
> From https://github.com/promises-aplus/promises-spec/issues/101#issuecomment-16657518
>
>> ```js
>> var promise = getDataFromServerUsingQ().then(function (data) {
>>    return $('.foo').animate('opacity', data.opacityLevel).promise().then(function () {
>>        return updateBackboneModelViaSomeThirdPartyLibraryUsingUnderscoreDeferred().then(function () {
>>            return tellServerThatTheUpdateSucceededUsingQ();
>>        });
>>    });
>> });
>> ```

This looks like a case for static resolution to me. Like this:

```js
var promise = getDataFromServerUsingQ().then(function (data) {
   return Q($('.foo').animate('opacity',
data.opacityLevel).promise()).then(function () {
       return Q(updateBackboneModelViaSomeThirdPartyLibraryUsingUnderscoreDeferred()).then(function
() {
           return tellServerThatTheUpdateSucceededUsingQ();
       });
   });
});
```

I favor this expression because it *explicitly* invokes Q's behavior
early and every use of 'then' is Q's 'then'. It costs 6 more bytes. Do
you have any examples where you *don't know* until runtime how many
thenables are wrapped?

>> If Q, as a proper Promises/A+ library, does recursive `[[Resolve]]`, this is a promise for undefined that will be rejected with the appropriate error if any of the operations failed. But if it did one-level unwrapping, this would be a QPFAUDPFAQPFU, and it would always fulfill---failure information would have to be manually extracted.

If each of the wrapped promises also did one-level unwrapping, Q would
not have to do dynamic unwrapping. If you use Q's assimilating
constructor, you don't need dynamic unwrapping. If one-level
unwrapping were the standard, all these libraries could interoperate
*without* recursive resolution.

If we're looking for the most flexible, straightforward, easiest, and
future-proofed semantics, it seems to me that decreasing the amount of
magic inside the abstraction is nearly always better.

Does that make sense?

David


More information about the es-discuss mailing list