Comments (13)
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.
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.
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.
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.
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:
- 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.
- 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:
- Adding another promise conceptually changes the resolution value, which is not allowed (promises are immutable once resolved!), and
- 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.
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.
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.
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.
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.
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:
- 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 resolvedepsDone
, i.e. when some condition is met. - 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.
@TobiaszCudnik it seems likely that you've found an acceptable solution. If not, please feel free to reopen this.
from when.
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.
@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)
- Cannot resolve module 'vertx' HOT 9
- JSPM build (SystemJS builder) failing on [email protected] HOT 3
- Does not work in NodeJS (ReactJS Native) HOT 12
- Visual Studio debugger breakes from adding When to WebPack bundle. HOT 13
- 3.7.7 breakes the build on Windows HOT 2
- no such file or directory .... es6-shim/makePromise HOT 2
- Global rejection events completely broken in a bundled environment (eg. Webpack)
- When not working in Jasmine tests
- when.reduce UnhandledPromiseRejectionWarning HOT 4
- Misleading unhandled rejection warning when using when.settle HOT 10
- npm install ! has errors HOT 1
- Usage hello world example fails with TypeError HOT 3
- Binding context to promise chain HOT 2
- Add react-native support HOT 2
- File 404 when try to use in browser environment
- Promise.js first function never runs HOT 2
- RTE TypeError after production build HOT 4
- Does function call also accept promises? HOT 4
- Current project status HOT 1
- npm install failing with 404 error code
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from when.