New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a dependant promise to an existing deferred #51
Comments
Hey @TobiaszCudnik. I think I need a little help understanding what you're proposing :) In your example, would you like to be able to trigger the resolution of p1 and p2 when p3 resolves? |
Sorry for not beeing clear, my expectation is to have one promise from 'd' resolved once all p1, p2 and p3 are resolved. In other words i would like to append one extra promise to 'all', but after a deferred is created. Does this make sense? If not i can work out some sequence diagram once im on a desktop. I think the used method name 'when' could confuse you, but thats an unexisting method, which i'm proposing :) |
Ah, ok, thanks for clarifying. I think I understand what you're looking for, so here's something I think may do what you want. You can think of when.all() as composing promises, and itself returns a promise, which can then be composed with other promises. var when, d1, d2, d3, joined;
when = require('./when');
d1 = when.defer();
d2 = when.defer();
// joined is now a promise that will resolve when d1 and d2 have resolved
joined = when.all([d1.promise, d2.promise]);
//...
// Later, join another promise
d3 = when.defer();
// Now, joined is a promise that will resolve only when d1, d2, *and* d3 have resolved
joined = when.all([joined, d3.promise]);
joined.then(function(results) {
console.log(results);
});
// Later ...
d1.resolve(1); // Nothing logged to console yet
d2.resolve(2); // Nothing logged to console yet
d3.resolve(3); // Logs: [[1, 2], 3] to the console
// Notice the extra array nesting, which may be a problem In terms of promise resolutions, I think that may do what you need, but the resulting nested arrays may not be what you expect, due to the two Let me know if that helps. If this Cheers! |
Hi, thanks for elaboration. I dont mind the result format, but there's one main problem with composing approach - changed reference to the first promise. Consider this (in CS): # this is our target
some_action = -> console.log "exec"
# some dependant promises
d1 = when.defer()
d2 = when.defer()
# p1 is our official promise
p1 = when.all d1.promise, d2.promise
# binding callback to the reference weve got at this point
p1.promise.then some_action
# we now want to add another dependent promise
d3 = when.defer()
# joining wont work for the some_action at this point
p2 = when.all p1.promise, d3.promise
# line below would execute some_action for the second time
# p2.promise.then some_action We could use a promise for a promise (which is handy in some cases), but in that one i think ability to extend existing deferreds would be much much helpful. Cheers! |
Ah, ok, I see what you mean. You'd like to modify an existing promise, rather than create a new promise. Two of the important properties of Promises/A promises a promise are: 1) They may only move from the pending state to the resolved state (which may be either "fulfilled" or "rejected"), and 2) once a promise has been resolved, it becomes immutable. Those things allow promises to make some strong and useful guarantees to their consumers. So, given that, I see a couple of tricky/problematic cases with "extending" an existing promise:
So, I definitely have some concerns about extending an existing promise, especially since there would be implications about the immutability of promises. That said, though, I'd love to hear what you think about these immutability concerns. Maybe there is a solution here if we talk through it more. FYI, here is a slight variation on my last example that solves the nested array result problem: var when, d1, d2, d3, joined;
when = require('./when');
d1 = when.defer();
d2 = when.defer();
// joined is now a promise that will resolve when d1 and d2 have resolved
joined = when.all([d1.promise, d2.promise]);
//...
// Later, join another promise
d3 = when.defer();
// Now, joined is a promise that will resolve only when d1, d2, *and* d3 have resolved
joined = joined.then(function(results) {
return when.all(results.concat(d3.promise));
})
joined.then(function(results) {
console.log(results);
});
// Later ...
d1.resolve(1); // Nothing logged to console yet
d2.resolve(2); // Nothing logged to console yet
d3.resolve(3); // Logs: [1, 2, 3] to the console |
I completely understand your concerns, although i would never want to extend a resolved promise (it doesnt make sense, as time doesnt go back). Although in a system im writing right now (and based on experience from previous ones), promise is build by many components synchronously in very mixed order of:
In such case i'm kind of blocked, as i cant add new dependencies after constructing the promise (which is a lazy property of an object). Of course it could be problematic to support rejections when extending already resolved promises, but this would be always a synchronous operation, so even exception handling can be used. Workarounds for now are two:
PS. From your example i suspect that |
Another cujo.js project, wire.js, does similar things to what you're describing. It recursively processes dependencies, loads them, instantiates them, configures them, etc. Internally, it uses Wire.js is a full-blown IOC Container, so depending on your needs, you may want to have a look to see if it can do what you need. If not, you may be able to apply some of the algorithms it uses. The main algorithm parses a specification that contains application components, some of which may be arrays of more components. Here's an example of how it processes an array. The Processing an object instead of an array can be done similarly (wire does this as well) by collecting promises and then using Would an approach like that work for your situation? Yes, I think you're right about the docs needing to be explicit about .then() (and .otherwise(), .always()) returning a new promise. Thanks for pointing that out! That behavior is part of the Promises/A standard, and is documented there, but I agree that when.js's API doc should also include that info. I created a issue #53 for adding that to the docs. |
Also, here's a fiddle that shows how the fact that |
I must say that i embrace the unobtrusiveness of the Promise pattern and solutions like wire.js are definitely too heavy. Please find the attached code as an example of my use case, where i gently mix deferreds with a property encapsulation giving me simple API to cooperate between the objects. The proposed method is named here class DataModel extends RequestModel
requests: prop('requests',
init: (set) -> set []
set: (set, request) ->
@requests().push request
# proposed Deferred/when API extension
# name is purely descriptive
request().ready @ready_deferred_().getWaitCallback()
)
class RequestModel
ready_deferred_: prop('ready_deferred_',
get: (get) ->
@ready() # init a promise
get() # return deferred
)
ready: prop('ready',
init: (set) ->
deferred = Promise.defer() # init deferred
@ready_deferred_ deferred # set deferred to it's property
set deferred.promise # set promise to this property
set: (set, block) ->
@ready().then block # set a callback to the promise
)
# All code below is synchronous
request1 = new RequestModel # construct 1 data obj with 2 requests
request2 = new RequestModel
data = new DataModel
data.requests request1 # add a new request, init all promises
data.ready -> # pass a reference to the promise
console.log 'data ready'
# add another promise extending the deferred
data.requests request2
# Error handling example
request3 = new RequestModel
try
data.requests request3
catch Exception # Resolved deferred cant wait for a new callback |
Hmmm, I'm not sure I fully grok what I understand that in your situation you wouldn't try to add new promises to an already-resolved deferred, but a general purpose library like when.js would need to cover all possible cases. It's not clear to me yet that that's possible without causing some serious confusion for developers. That said, I wonder if there's a simpler solution for your "adding more promises to a deferred" case. It seems like it would be possible to maintain two independent, but related pieces of information:
So, you could create var deps, depsDone;
deps = [];
depsDone = when.defer();
// At this point, depsDone.promise can be given out as needed. Since the
// deps array has been decoupled.
// ...
// As needed push dependents onto deps
// Could be done here, or in some other code, even asynchronously
// in future event loop turns, whatever fits your needs.
deps.push(getDependent());
// ...
// When you know all dependents have been collected *but they
// don't have to be resolved yet!*, ensure depsDone will resolve
// once all dependents resolve.
// This will resolve depsDone once all deps have resolved
// This will reject if any dependent promise rejects
depsDone.resolve(when.all(deps)); That seems pretty simple and flexible to me since it decouples the Would something like that work? |
@TobiaszCudnik it seems likely that you've found an acceptable solution. If not, please feel free to reopen this. |
It seemed more appropriate to append my question to this thread than open a new once since it's very similar. I'm used to using async for parallel/sequence based processing and would like to make the switch from callbacks to promises, so I've been checking out when. The recommendation on this thread seems pretty cumbersome. Is there something like https://github.com/caolan/async#auto available in when? I know I can just wrap the callback, but it seems like having this in when would be a nice feature. |
@pward123 Async's auto is basically a tree fold and/or directed graph traversal. when.js doesn't offer that currently, but it's the kind of thing can be build pretty easily on top of promises (just as it can be built without promises for a synchronous tree fold). For simple things, it's pretty easy to use when/parallel and when/sequence. For example, here is the same async auto example. Our approach for when.js has been to start at the low level building block, the promise, and build up useful abstractions on top of it, like parallel and sequence, etc. If there's widespread need for a declarative task graph traversal, we can certainly consider providing it. Building it as an external lib could also be a nice idea. If you'd be interested in tackling that, please let me know, and I'd be happy to help/answer questions, etc. |
I would like to to add a dependant promise to an existing deferred, sth similar to when.all(), but done after it (although prior to resolving the deferred, of cource).
Eg
Thanks!
The text was updated successfully, but these errors were encountered: