Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Only un-wrap one layer in `[[Resolve]]` #101

Closed
ForbesLindesay opened this Issue · 53 comments

8 participants

Forbes Lindesay Domenic Denicola Meryn Stol Juan Ignacio Dopazo Mark S. Miller Bergi Brian McKenna Mariusz Nowak
Forbes Lindesay
Collaborator

I'd like to propose we re-visit the discussion surrounding the recursive nature of the new resolution algorithm. I understand that this is in-line with how many promise libraries currently operate, but I think we should consider whether it has to be that way as I have now managed to find some fairly compelling use cases of promises for promises and it does offer great gains in compatibility with monad systems.

I realize this is a contentious issue and these threads can quickly get very long and full of circular arguments. To help with this I have created 2 gists:

I remain personally fairly on the fence over this and have attempted to make both arguments as strong as I can.

For the first 36 Hours (until 2013-04-17T15:00:00Z) I would like people to refrain from commenting in this issue unless said comments are completely neutral. I would also like you to refrain from posting any rebuttals in either gist. Please read both gists, and add any arguments in favour of promises for promises to the For gist as comments and any arguments against promises for promises to the Against gist as comments.

I will attempt to merge any arguments I see in the comments into the main document, and will then delete those comments. The aim is to end that 36 hour period with two arguments that are both concise enough for everyone to read more or less in full and as solid/persuasive as they can be.

I would also like to request that everyone reads (or re-reads if you read them a while ago and need to refresh your memory) both of the following 2 documents:

  • The Proposed New Promise Spec - This is the spec we are discussing modifying, so it's important everyone understands that this is the starting point. We are considering changing this document, so it needs to be an improvement, not just no worse.
  • The Real World Spec - my attempt at re-writing the fantasy land spec to be from approachable for people with a background in JavaScript rather than Category Theory (alternatively the original if you find that easier to digest)
Domenic Denicola
Owner

First, we need to be much more precise with our usage of "promise" versus "thenable."

I think it's important to note that the spec does not prohibit promises for thenables. An implementation is allowed to create such things, and operate with them, and even hand them out to others. However, the spec does specify that if a non-promise thenable is assimilated, i.e. an unrecognized thenable from another implementation, it must be flattened. This behavior is meant to preserve boundaries between implementations. That's all!

Thus, allowing promises-for-thenables is an implementation choice left up to individual libraries. However, when libraries interact, such objects must not be forced upon an implementation that does support or allow such objects, i.e. that latter implementation must be protected from the design choices of a thenable-for-thenable producer.

(I had another paragraph here, but am moving it over to the gist in an attempt to respect the rules, since it ended up sounding more like advocacy than clarification.)

Meryn Stol

So the "argument collection" period seems to be over, and as I have said earlier (which I probably shouldn't have b/c of neutrality - for which I apologize) I feel for both standpoints. That's why I think it may be be counterproductive to turn this into a shoot-out of "are we going to have promises for promises - or not?". I think we would do best to first establish that we can't have both, or if we can't have both requirements in the way that that they are stated now, if we can't have the benefits of both with some creative solution.

I already suggested for the "promises for promises" proponents to simply "cede ground" (or stop attempting to take the current castle, depending on how you view the current state of the spec), and instead attempt something that leaves the "no promises for promises" proponents satisfied.

Most notably for me is that the people against "promises for promises" want something simple, while the proponents want something suitable for advanced use cases. Plus all or most have a FP background which I think could mean that calling the then function could well end up being "encapsulated" in a function which responsibility it is to call the then method.

As illustration, I want to present this piece of evidence:

function liftA2(pa, pb, f) {
    return ap(pb, map(pa, function(a) {
        return function(b) {
            return f(a, b);
        };
    }));
}

This is basically code from @pufuwozu attempted to make more readable in CoffeeScript and with slightly more descriptive variable names. This is taken from a code exampel by @pufuwozu .

The point is that @pufuwozu and others might see ways (beyond my imagination) to make great use of promises/nomads without ever having to see a single then (once a few "primitives" like map and ap have been developed). This suggests to me that for them, it may not matter that much in practice if the interface to the functionality now provided by then is by any means simple. It may very well be extremely cumbersome. But it gets hidden away, so it doesn't matter.

These are some ideas. 1 and 2 I mentioned earlier.

  1. We could make up a new method later, that unlike then only unwraps one layer. In practice, that would mean that the unwrapping would need to happen when the then method is called, instead of right after any onResolved or onRejected callback has returned a value. I think this would naturally be complemented with some kind of "flatten" or "unwrap" method, so that then would be equivalent to unwrap().later().

    I need to admit right away that I do not know exactly if this is technically possible. I've never worked on a Promise library (whatever the flavor). Intuitively, I think it can.

  2. To address the problem of potentially misbehaving promises, we could decide to require a library to not unwrap all promises and thenables it comes across. The spec could require a library to special-case its own promises (recognized by instanceof, or similar means). It could also require a library to special-case any promise which carries a "isPromisesACompliant" label.

    Now Domenic has already told me that at least some implementers (RSVP in this case) don't want to have promises for promises at all, and therefore don't bother to special case their own promises. This may not be the way to go.

  3. A third option would be is to introduce a dontTouchMyThen label, which implementers would respect, not out of agreement per se, but simply out of courtesy.

    I'd like to think of it as a minimalistic "robots.txt" for promises. I.e. the Web was designed with the idea that GET was safe, and therefore web crawlers could arise. The idea of a crawler is unthinkable without having a pre-agreed on safe method. However as it turned out, not everyone who'd like his pages indexed. Two ways where invented to address this problem: <meta name="robots" contents="noindex,nofollow"> and robots.txt. I actually do not know which came first, but that's largely beside the point. They were both means to address long-established and normally very desirable behavior of web crawlers, by allowing a way to change their default behavior.

  4. Lastly, also inspired by web technology, is the idea of sending along a "caller-identifier" (i.e. the User-Agent for promises). As web servers are able to change what they return on basis of user-agent sniffing, so could thenables. Thenables could turn some calls to their then into (effective) noops, depending on the "caller-indentifier", which would be the third argument to then. Perhaps better than a "caller-indentifier" would be a "purpose-indentifier". The purpose-identifier would specificy if the value is actually needed or not. Looking at like this, the purpose-identifier could be a simple boolean. It wouldn't even hurt extensibility of the meaning of the third argument, if all it would do is lock-down values for exactly true and false. Later the third argument could become a string or an options object.

    Note that with "effective noops" I don't mean they're actually noops. Internally, they might need to do some magic. Keeping track if their then has been called earlier at some point or so. I don't know. It's hard to imagine for me at this point. But I see potential here.

What do you think?

Meryn Stol

My lack of experience with Promise implemantions may become awfully clear...
I think turning then into an (effective) noop (as proposed in 4) would cause infinite recursion in the standard promise resolution algorithm. It probably needs to be combined with something equivalent to a dontTouchMyThen label to stop recursion.

Forbes Lindesay
Collaborator

Right, I now want to open this up to the floor for comments. Please ensure that you re-read each document. Many of them have changed significantly. You don't need to read the comments unless you want additional clarification. The comments in FOR discuss what it means for a promise to be lazy. The comments in AGAINST are largely about how you can recognize a foreign promise. Neither discussion relates directly to the issue at hand.

I would now like to propose that all discussion take place in this issue.

Forbes Lindesay
Collaborator

I want to begin with the clarification that the current spec does not prohibit promises for promises. It also doesn't prohibit the then method only unwrapping a single layer of promises that are recognised in some way. It's only thenables which must strictly be unwrapped recursively. The specification also leaves how you recognise true promises completely open. As such one option would be a duck typing/branding test. Promises that want to only be unwrapped one layer could provide a .supportsPromisesForPromises label and promise libraries that want to support this could choose only to unwrap one layer for such promises.

The advantage of this is that such a specification could be developed independently of any existing specifications and could then prove its usefulness through use in real applications.

Forbes Lindesay
Collaborator

It would also mean that a library that didn't want to allow promises for promises could still recursively flatten such thenables/promises at the boundary.

Meryn Stol

@ForbesLindesay Regardless of the spec, doesn't it also matter what the de-facto behavior is today? I mean, if the most prominent Promise libraries now all voluntarily choose to unwrap all layers, then that will be the expected behavior of consumers of said libraries. I.e. a consumer would expect that if it call "then" on some promise, the callback it supplies will get called with a non-promise value in return. Leaving this part of the spec open could be harmful then. Note, I'm not sure about the harmfulness. I've yet to gain experience with doing such advanced work on promises. Right now the only promises I've worked with were the promises I've created myself (by means of RSVP, that is).

Meryn Stol

Perhaps it calls for two excplicit "variants", "additions" to the spec, because both of these can be regarded as something positive, but mutually exclude each other:
1. Guarantees unwrapping of all promises and thenables.
2. Supports promises for promises.

I think it would be a good thing if a programmer could see up front what kind of library he's getting: Variant 1 or variant 2. Then both could benefit from an explicit mark. Solely developing a distinct "mark" for (2) leaves ambiguity in the behavior for the other libraries. I.e. do they provide any guarantees on unwrapping all promises and thenables?

Put differently, I think RSVP currently does more than it is asked for and - based on Domenic comments - this "more" is seen as a good thing. Better be explicit about it then. Simplicity is a selling point.

(I'd personally opt for a library which respects the .supportsPromisesForPromises property.)

Meryn Stol

Actually the library I'd choose would depend on what I'd be using it for. If I'd be using the library for "scripting", I'd choose a library that guarantees unwrapping, so that if I call "then" I get a value that I can use straight away. If I'd be developing a library based on promises on my own (say promised-builtins) then I'd choose for the non-unwrapping variant, so I can make use of laziness inside.

Actually, I think when coded right, a library of variety 2 could be easily made to adapt behavior of variant 1. All it needs is the then function on the promise prototype to be adjusted. This could be made as simple as requiring a single call after someone has loaded the lib. I.e. SomePromiseLib.autoUnwrapAllPromises().

Juan Ignacio Dopazo

attempted to make more readable in CoffeeScript

@meryn please don't. This is a JavaScript specification, let's keep the discussion in the same language.

@ForbesLindesay said:

I want to begin with the clarification that the current spec does not prohibit promises for promises. It also doesn't prohibit the then method only unwrapping a single layer of promises that are recognised in some way. It's only thenables which must strictly be unwrapped recursively.

This may seem that way, but in practice the story is very different. The lack of branding (which we discussed a while ago) left libraries with three options:

  1. Recognize everything with typeof obj.then == 'function' as a promise
  2. Recognize only its own promise implementation as valid and the rest as thenables
  3. Keep a list of valid implementations

(3) is simply impractical and many times impossible. Q does (2). YUI, when and most notably DOMFuture do (1). In fact, take a look at the text in DOMFuture:

If JavaScript IsCallable(then) is true, run these substeps then terminate these steps:

Let resolve be a future callback for the context object and its resolve algorithm.

Let reject be a future callback for the context object and its reject algorithm.

Call the JavaScript [[Call]] internal method of then with this value value and resolve and reject as arguments.

Emphasis mine. If I understood correctly, this effectively means that whatever thenable or promise will be flattened.

So we have two options going forward:

  • Standardize a way to recognize valid promises. For example: Object.prototype.toString.call(obj) === '[object Future]'. This has a big open question: if in the future JS gets await, will it use duck typing or follow this spec and lose the ability to await thenables.
  • Forget about recursive assimilation and press the WHATWG to change the resolve algorithm.
Domenic Denicola
Owner

@meryn I'm sorry, but you've thrown such amazingly large walls of text up that I can't really keep up. If you want to summarize your points to like 200 words maybe we could all comprehend them better.

@juandopazo DOMFuture will almost certainly not change, and if await comes it will likely use duck-typing, based on discussions I've had. Q will also likely converge on (1). Finally, it's impossible for an object to change what Object.prototype.toString.call(obj) returns, so that particular branding mechanism isn't really feasible.

But in practice vs. in theory doesn't really matter. The point is, many libraries in practice don't want to allow promises-for-promises. That includes, apparently, YUI, DOMFuture, WinJS, and when. (Q will join them soon, most likely, and DOMFuture has an inconsistency that it allows creation of promises-for-promises via accept, sigh.) But if people are really excited about promises-for-promises, they can create a library that allows them! They just have to stay inside the boundaries of one library while they play with that stuff.

Maybe let's approach this the other way: what possible benefit would you derive from a JQPFAQPFAWJPFADFFAN? If each of those implementations recognized the others as conformant, then you could create such an entity. Why is that a good thing? What does it help you with?

Meryn Stol

@meryn please don't. This is a JavaScript specification, let's keep the discussion in the same language.

@juandopazo I replaced it with original code from @pufuwozu . Works too. :)

Mark S. Miller

@domenic writes

Q will also likely converge on (1).

I am very distressed to hear this. Likely it will mean that we will have to fork Q, as I don't see how my security goals can be met within (1). If this is not the right forum for this issue, please post a link to the right one.

Domenic Denicola
Owner

@erights perhaps I misspoke; I meant only from the perspective of the [[Resolve]] algorithm, not more generally. Q(thenable) will still ensure that the result is a Q promise. If you're still concerned, let's definitely work together and not necessitate a fork (probably outside this issue, perhaps by email).

Meryn Stol

@domenic Read the first sentence of my bullet points. That will do as a summary I think. The summary of the summary is that I want to look for options to keep then mostly the same, but at the same time devise ways to accomodate for "promises for promises".

Another option (5), I just came up with: By default, let then unwrap all layers, but if passed a third argument (beyond onResolved and onRejected) - call it dontUnwrap - set to true, it won't unwrap all layers. Just like my suggestion for a new later method on promises (idea 1 in my previous post), this would require implementors to run the "promise-resolution" procedure at the time then is called, not directly after the onResolved callback has returned. I hope this is technically possible. But I've never implemented a promise-lib!

In general, I feel that the non-simple variant (the unwrapping behavior for then is certainly simple.. or should I say easy?) may be a little ugly, because it will often be hidden away behind abstractions (why I mentioned the liftA2 example).

Domenic Denicola
Owner

@meryn what value do you derive from a JQPFAQPFAWJPFADFFAN? What problems does it help you solve?

Mark S. Miller

@domenic Ok, that wouldn't break security as far as I can see. But it would still break both promise pipelining (section 8 of http://www.erights.org/talks/promises/paper/tgc05.pdf . Nice summary at http://en.wikipedia.org/wiki/Futures_and_promises#Promise_pipelining ) and laziness. Please look again at the logic of Q.makeRemote in makeQ.js.

Meryn Stol

@domenic I'm not arguing for a JQPFAQPFAWJPFADFFAN :) I'm arguing for support for both recursive and non-recursive unwrapping. The possibility of getting a JQPFAQPFAWJPFADFFAN may be an accidental consequence. This is something the people using the non-recursive unwrapping "mode" must deal with. It shouldn't be your worry. :) I personally will make sure I'll keep sanity in any "layered" promises. But "lazy promises" sure would be nice!
And aside from that, I'm of course curious what the Monad-guys will come up with. This wasn't my proposal. I'm just clinging on to something that looks nice to me. :)

Mark S. Miller

@meryn writes

But "lazy promises" sure would be nice!

No conflict here, as demonstrated by the existence proof of Q.makeRemote in makeQ.js

Mark S. Miller

@meryn No conflict that is, as long as a promise library does not unwrap its own promises, which is the issue just raised about Q.

Meryn Stol

@erights I need to study Q.makeRemote .

My reference point is an idea of mine for making "Promised Builtins". Basically proxies for builtins that behave just like their real counterparts. I think it would be great if I could pass them along without wondering if some are lazy, and some are really already in the process of being resolved (i.e. the async calls have been fired off). And if make a call to then, I'm sure to get the real, fully resolved value in either case.

I mentioned requiring implemenators not to unwrap their own promises in (2) of my earlier post. However, I do think that would result in a more complex API from the consumer point of view. The outward simplicity is the thing that @domenic likes a a lot, and I do too.

Meryn Stol

@erights where do I find "makeQ.js"?

Meryn Stol

Also, re JQPFAQPFAWJPFADFFAN: I think that with the proposed rule to only unwrap one layer, compliant libraries can never be the cause of this. Just replacing one foreign "promise layer" with a native promise-layer means the number of layers will stay equal. It won't decrease though. Any existing layering will remain. But this happens to protect the poor little lazy promise who doesn't want its then called until its value is absolutely required.

I think the most typical case will be that most of the time, most promises will be single layered (i.e. they would directly resolve to a value) and some will be double-layered promises of promises. The outward layer would effectively protect the inner layer from being touched. I don't see more than two layers happening in practice. At least, it's beyond my imagination of usefulness.

Juan Ignacio Dopazo

@domenic

Finally, it's impossible for an object to change what Object.prototype.toString.call(obj) returns

(facepalm) Sorry, I meant promise.toString() === '[object Future]'. I agree that not trying to discern between promises and thenables is the way to go.

Why is that a good thing? What does it help you with?

You'd have to navigate the 6000 comments of the last few days to answer that. If I can put it in one sentence it would be: "doesn't break the monad laws and helps by introspection to discover opportunities for performance improvements".

Bergi

@meryn:

By default, let then unwrap all layers, but if passed a third argument - call it dontUnwrap - set to true, it won't unwrap all layers

Why not the other way round? I'd be fine with that.

I mean, of the many people who have argued against promises for promises and like the synchronous-code metapher, who of you has ever encountered a promise/thenable for a thenable? I'd assume they just don't exist in "your world", so you don't have to worry about obtaining one. And should you really (have to) work with a lib that (maybe) uses/supports this crazy feature ("has that bug"), then just set a "and-please-flatten-everything-for-me" flag when assimilating one of these promises.

Those of us who are (planning to) use promises for promises in everyday code certainly don't want to have to care about always filling out that third parameter.

Domenic Denicola
Owner

I'd assume they just don't exist in "your world"

They don't exist in our world because we flatten them. If we didn't, they'd be everywhere, as anyone who's tried to use jQuery and Q or when and Mongoose or WinJS and RSVP together can attest!

Meryn Stol

@domenic Could you explain the typical process of how these layered promises come into existence?

Would they still come into existence if every time a foreign promise was encountered, only the (foreign) outer layer gets replaced by a native layer? Because that's what up for discussion I think. (the outer layer could very well be the only layer, but that's beside the point).

Forbes Lindesay
Collaborator

@meryn They wouldn't come into existence if all promise libraries worked like this and supported unwrapping one layer with each call to then. The reality is that many implementations don't unwrap at all. A chain of two sequential http requests could easily cause this problem if made using one of the non-conformant libraries. The current system protects you from the non-conformant libraries.

Forbes Lindesay
Collaborator

@juandopazo

This may seem that way, but in practice the story is very different. The lack of branding (which we discussed a while ago) left libraries with three options

I think maybe I wasn't clear here. I wasn't suggesting we use branding to say "I'm a promise so you should unwrap me" since there are loads of promise objects which lack such branding. I was suggesting we brand this new variety of promises that supports/requires nesting. This seems fine because there aren't many such libraries at the moment so it's not too late for them to all agree on something. It should also be easier because (in order for it to be useful) it seems to be necessary for them to support other operations such as of, chain and empty

Domenic Denicola
Owner

If other paradigms aside from promises, such as those using of/chain/empty, need this capability in those operations, then IMO they should include information about that in their specs for those methods. Such considerations should not impact the behavior of then.

Juan Ignacio Dopazo

@ForbesLindesay no, sorry. You were clear. I was just trying to make a point.

Forbes Lindesay
Collaborator

@domenic that seems like a sensible idea to me.

For anyone who's interested, a better way of building the Request API might be to have a separate body property be a promise and resolve that to get the content.

Meryn Stol

The current system protects you from the non-conformant libraries.

@ForbesLindesay
I think recognizing comformant libraries with a isPromiseAPlusCompliant property would suffice then.

Promises that want to only be unwrapped one layer could provide a .supportsPromisesForPromises label and promise libraries that want to support this could choose only to unwrap one layer for such promises.

While it of course doesn't matter for an implementation what exact "string" is used to set the property, I don't think supportsPromisesForPromises is the appropriate label. After all, the reason the recursive unwrapping is in place is to protect from libraries that don't bother to unwrap the promise they get before creating a new one. So that behavior is the behavior that should be declared explicitly. The label in effect would be saying: "I have not made the world any worse, you don't have to clean up after me."

I don't say my variant is right. Maybe coupling to the Promise/A+ "brand" is undesirable.
I think then we should go for something like notUnnecessaryNested. Do you get what I'm saying?

Domenic Denicola
Owner

@meryn I think you're still missing the point. We want to allow implementations to not do promises-for-promises. We can't specify some kind of magic brand which then allows those protections to be bypassed. In your phrasing, "I have not made the world any worse" is not something all libraries agree on. In my view, any library that produces promises for promises has made the world worse, and I need to put a stop to that craziness before it crosses into my own library.

Domenic Denicola
Owner

Again, I'm a bit surprised this is so hard. As I emphasized originally, you can do promises for promises as much as you want. You just need to stay within your own library. Things get normalized when crossing library boundaries, not before.

Meryn Stol

We want to allow implementations to not do promises-for-promises.

Is this established? Is there a "we" who wants to allow implementations NOT to do promises-for-promises?
And what is meant by "do"? Does it mean that there's a "we" that wants to allow implementations not to respect promises for promises? I thought it was all up for debate.

You're entitled to your opinion of course, as are many others who might agree with you.
If I'm way off, then I'll think I'll just refrain from commenting further because I might be just adding noise.

Forbes Lindesay
Collaborator

@meryn You're still missing the point as @domenic says. We aren't going to punch a magic hole through the existing implementations. I think what we can go for is go for a solution where all the libraries that support promises for promises can work together, so the idea can be experimented with properly.

Forbes Lindesay
Collaborator

Those essages crossed in the post slightly. We are discussing that option, but I think it's highly unlikely to succeed in that form. A great many libraries explicitly don't want to support promises for promises.

Meryn Stol

@ForbesLindesay Thanks for pointing that out to me. I think I'll indeed keep quiet from now on. I hope that I've at least have added some value to the discussion. Otherwise, sorry for the noise. Let me know if there's a new spec up for debate. :)

Brian McKenna

I would not like Promise libraries to provide an of method if they do not support promises of promises. My only request. Thanks.

Juan Ignacio Dopazo

Just to clarify the consensus part, at least I am not sure if flattening is the best approach. I haven't explored the problem space enough. While on one hand I appreciate the arguments behind flattening, I can't shake the feeling that dismissing 30 years of research in monads is a pity.

Bergi

@domenic:

If we didn't flatten them, they'd be everywhere, as anyone who's tried to use jQuery and Q or when and Mongoose or WinJS and RSVP together can attest!

Flattening the one layer should prevent that, shouldn't it? Can you make a (code) example of how using these libs together results in an accidentally nested promise (if they didn't do recursion)?

@ForbesLindesay:

They wouldn't come into existence if all promise libraries worked like this and supported unwrapping one layer with each call to then.

That's what I was suspecting, and what I think we should specify if it's gonna work. And providing an alternative way for assimilating non-conformant thenables, instead of needing to assume that every thenable is non-conformant.

The reality is that many implementations don't unwrap at all.

You mean they implement then as a pure map? Uh. However, could you show me any examples of such libraries, and code in them using their non-conformant then so that nested promises come out? I mean, I would expect everybody to assimilate those promises things into conforming promises as soon as possible, and not using their then method at all.

Bergi

@pufuwozu wrote:

I would not like Promise libraries to provide an of method if they do not support promises of promises.

Hey, that could be a clever idea to detect conforming promises/thenables! I'd think of a [[resolve]] procedure such as

if (value.then is a function) // thenables, including promises
    if (value.constructor.of is a function) // it's a monadic promise!
        unwrap exactly one layer
    else
        assimilate by recursively running [[resolve]]
Domenic Denicola
Owner

Can you make a (code) example of how using these libs together results in an accidentally nested promise (if they didn't do recursion)?

var nestedPromise = $.when(Q(5));

var anotherOne = $.when(5).then(function () { return Q(10); });

var realisticExample = $('.foo').animate('opacity', 0).promise().then(function () {
    return getDataFromServerUsingQ();
});

Three levels:

var _Deferred = require('underscore.deferred');

var threeLevels = _.when($.when(Q(5)));

Etc. as I pile on more and more libraries.

Bergi

@domenic:

  _.when($.when(Q(5)));

OK, but nesting constructors is something you wouldn't do, would you? Actually I'd say something like that (maybe with of) would be the preferred way to explicitly nest promises :-) However, this would be resolvers-spec, not what we're discussing here…

Your anotherOne or realisticExample is much more interesting. So jQuery doesn't unwrap Q promises but treats them as values? Fine (not that it's unexpected). But how would go about fixing that? I only can think of either using

ConformantPromise.assimilate($('.foo').animate('opacity', 0).promise())
  .then(getDataFromServerUsingQ)
  .then(…);

or

ConformantPromise.recursiveAssimilate(realisticExample).then(…);

as just going on with jQuery's realisticExample.then(…) would not work. So we have to fix it explicitly anyway, don't we?

Domenic Denicola
Owner

@bergus yes, you would need to do a transformation into a realistic example for the three-level case; I left that as an exercise to the reader.

Let's try again. This will also address your other question.

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

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.

Mariusz Nowak

I use promises a lot, worked out a lot of complex async flows with them, and I understand this discussion as:
Reconsider whether promise is a promise, maybe it isn't ;-)

It's a dispute over a fact whether a promise is a real value (type that we treat same on same level as we treat an array, string, number etc.) or not (as it's treated now by flattening it to resolved value whenever possible).

I take that if we treat it on pair with other types, then we're actually no longer talking about promises but about some other custom type, that serves uncertain purpose. It will no longer be a promise as it will no longer be aid for asynchronicity (as it is now with currently accepted characteristics).

I'll add to @domenic example, copy of my example from Promise for Promises gist, of how with current common usage, lazy promises (not flattened) will behave:

var lintDirectory = function (path) {
  return readdir(path).map(function (fileName) {
     return readFile(fileName).then(lint);
  });
};

// No lazy promises (as current implementations do)
lintDirectory(path).then(function (reports) {
  console.log(reports); // [...] array of lint reports
});

// Lazy promises
lintDirectory(path).then(function (readdirPromise) {
  readdirPromise.then(function (readFileMapPromise) {
    readFileMapPromise.then(function (lintMapPromise) {
      lintMapPromise.then(function (reports) {
        console.log(reports); // [...] array of lint reports
      });
    });
  })
});
Domenic Denicola
Owner

Indeed, I like the way @medikoo phrases it. There's---practically speaking---nothing wrong with being a monad on the category of non-thenables.

Mark S. Miller

There's---practically speaking---nothing wrong with being a monad on the category of non-thenables.

Excellent!

Forbes Lindesay
Collaborator

I wonder if #103 factors into this in any way. It allows a promise to force the library not to unwrap it, but I can't see any way that it can then also fulfill with a value, rather than itself.

I think the idea of them being monads on the category of non-thenables is really nice.

I hope that someone will create a promise library with an of method and a chain method that does single level unwrapping, but my impression is that most libraries fit into one of two categories:

  1. Promises for values, that really don't see any value in a promise for a promise.
  2. Promises for remote values, where they always resolve to themselves (fixed by #103)

As such, none of the current libraries see any value to unwrapping just a single level. The spec can therefore remain as is currently proposed and recursively unwrap thenables.

If anyone has anything significantly new to add, I will re-open the issue so the discussion can continue. Otherwise I now consider this closed.

Mark S. Miller

@ForbesLindesay writes

most libraries fit into one of two categories

Q fits into both of the categories you list. Q far references are indeed fulfilled "Promises for remote values". Since they are fulfilled, they need to invoke their .then's success callback. But their fulfillment value is remote, so the most fulfilled representative that can provide locally are themselves, so "they always resolve to themselves".

But a Q far reference is not fulfilled by itself, it is fulfilled by the remote value. In no case is a Q promise fulfilled by a promise, even though it looks like that locally through the .then API. This is perhaps clearer when looking at the .there API http://wiki.ecmascript.org/doku.php?id=strawman:concurrency#there , where the success callback is always called with a non-promise.

Forbes Lindesay
Collaborator

@erights I would actually suggest that Q is essentially 2 libraries that happen to be published as one npm module and share a lot of source code. It creates 2 types of promises and each promise fits into one of the two categories, but not both.

The fulfillment value of a promise is determined using the following:

function logFulfillmentValue(promise) {
  promise.then(function (fulfillmentValue) {
    console.dir(fulfillmentValue);
  });
}

based on that, fairly intuitive definition (which I think aligns well with the spec), your far references fulfill to themesleves. I think the wording you've used of

fulfilled by itself

is a little confusing, the promise doesn't do the fulfilling, it has the fulfilling done to it, but it is fulfilled with itself as the fulfillment value.

At any rate, this is off topic here, I'm happy to discuss it elsewhere some time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.