Code Monkey home page Code Monkey logo

Comments (13)

briancavalier avatar briancavalier commented on May 19, 2024

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?

from when.

TobiaszCudnik avatar TobiaszCudnik commented on May 19, 2024

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

from when.

briancavalier avatar briancavalier commented on May 19, 2024

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 when.all() compositions. You could flatten the resulting array yourself, of course.

Let me know if that helps. If this when.all() approach won't work for your use case, let me know, and we can discuss the possibility of adding something like what you proposed.

Cheers!

from when.

TobiaszCudnik avatar TobiaszCudnik commented on May 19, 2024

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!

from when.

briancavalier avatar briancavalier commented on May 19, 2024

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:

  1. Consider the case where you try to extend an already-resolved promise (whose observers may or may not have already been notified) with an additional unresolved promise. Based on the Promises/A spec, this case can't be allowed, so it'd have to fail (maybe throwing an exception) immediately.
  2. Another odd case is where you extend an already-resolved promise (again, whose observers may have already been notified), with an additional resolved promise. This is problematic in a couple of ways:
    1. Adding another promise conceptually changes the resolution value, which is not allowed (promises are immutable once resolved!), and
    2. if the additional promise is in a different state (fulfulled or rejected) than the original, it's not clear what this means, but also seems like this can't be allowed because it could potentially change the resolution state of the original 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

from when.

TobiaszCudnik avatar TobiaszCudnik commented on May 19, 2024

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:

  • gathering promise dependencies (eg scripts to download)
  • passing the promise reference to components interested in them (eg ready state of a controller)

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:

  • general promise that there will be a promise with all dependencies (lots of nesting)
  • being sure that first bind to a promise is done after all dependencies are gathered (limits the flexibility)

PS. From your example i suspect that Promise#then returns also a promise, which is sth missed in the API docs i think.

from when.

briancavalier avatar briancavalier commented on May 19, 2024

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 when.map(), when.reduce(), and when.all() and a simple recursive algorithm to gather component dependencies and then process them.

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 createItem function is the recursion entry point, so it creates an array of components by using when.map to recurse into createItem for each thing in the array. And since script loading is inherently async, when.map can return a promise for the entire array of components immediately, and it will resolve once all elements in the array have been completed.

Processing an object instead of an array can be done similarly (wire does this as well) by collecting promises and then using when.all. In when.js 2.0, there will be versions of map, reduce, and all that operate on object properties in addition to arrays.

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.

from when.

briancavalier avatar briancavalier commented on May 19, 2024

Also, here's a fiddle that shows how the fact that .then() returns a new promise allows Promises/A forwarding. It's a very powerful thing once you get accustomed to using it!

from when.

TobiaszCudnik avatar TobiaszCudnik commented on May 19, 2024

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 Deferred#getWaitCallback, which i'm pushing to the Promise#when of all composed objects.

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 

from when.

briancavalier avatar briancavalier commented on May 19, 2024

Hmmm, I'm not sure I fully grok what getWaitCallback() would do. Could you explain a bit more, or maybe provide another example in plain JS (I'm not a coffeescript user, sorry). I'm def interested in understanding what you're proposing.

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:

  1. A deferred, call it depsDone, that represents being "done" with all dependencies. Since you've said that you wouldn't ever need to add promises to an already-resolved deferred, I'm making the assumption that you'll be able to know the right time to resolve depsDone, i.e. when some condition is met.
  2. An array, onto which you will push dependent promises, instead of using some as-yet-unimplemented "promise joining" machinery.

So, you could create depsDone, and give out depsDone.promise to any interested parties. You could also collect dependent promises into an array. When you've collected all dependent promises, you could use when.all to know when they've all resolved, then simply resolve depsDone. Something like:

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 deps array from depsDone. You can give out depsDone.promise as needed at any time, but still maintain control over when it resolves.

Would something like that work?

from when.

briancavalier avatar briancavalier commented on May 19, 2024

@TobiaszCudnik it seems likely that you've found an acceptable solution. If not, please feel free to reopen this.

from when.

pward123 avatar pward123 commented on May 19, 2024

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.

from when.

briancavalier avatar briancavalier commented on May 19, 2024

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

from when.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.