-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Implement Promise.using #65
Comments
+1 I think this is a great idea. Look into the implementation in issue #51 |
For reference, to support arrays of resources (code below): http://pastebin.com/tKRykm2x function using() {
var resources = [].slice.call(arguments, 0, arguments.length - 1);
if (resources.length < 1) throw new TypeError();
var fn = arguments[arguments.length - 1];
if (typeof fn !== "function") throw new TypeError();
var promiseForInspections = Promise.settle(resources);
promiseForInspections.then(function(inspections) {
return inspections.map(function(inspection) {
if (inspection.isRejected()) {
throw inspection.error();
}
return inspection.value();
});
}).spread(fn).finally(function() {
//.value() never throws here
ASSERT(promiseForInspections.isFulfilled());
var inspections = promiseForInspections.value();
inspections.forEach(function(inspection) {
if (inspection.isFulfilled()) {
var resource = inspection.value();
try {
disposer(resource);
}
catch (e) {
//Not sure what to do here
//at the very least we should keep closing the rest of the resources
//but we cannot change the rejection reason because this is in .finally
}
}
});
} The question is whether it's worth it to support multiple resources. Handling just one is way more efficient, from a code point of view, and maybe performance. |
Looking into Python's Here is the Here are the semantics of In Python, compound with acts in the following way:
Is equivalent to:
This is of course not a solution since in Python Every other language that allows a compound p.s. Whoops on the commit reference. That's my bad. (How did it even get here? I didn't commit it, I deleted the repo and made a new one because github refused to... nevermind) |
Then I'd rather have only one resource possible. Having fake asynchronous looks like more dangerous than nesting. |
Multi-resource with means that your resource-allocating functions must never throw.
is the same as
This illustrates better that fn1 and fn2 are invoked before we even start executing But - what if You could argue that function createDbConnection(endpoint) {
var blah = parse(endpoint); // potentially throws
return connectToDbPromise(blah.host, blah.port);
} This |
|
@petkaantonov the same issue with multiple resources still applies. Basically, the resource allocator functions must not throw, which is hard to guarantee for all resources... |
What functions? |
Here is a realistic looking example readConfig().then(config =>
using(apiEndpoint(config.url), createDbConnection(config.db), (endpoint, db) =>
endpoint.getStuff().then(withThis(db, db.writeStuff)))) Which is equivalent to readConfig().then(config => {
var pEndpoint = apiEndpoint(config.url),
pDb = createDbConnection(config.db); /* throws */
return using(pEndpoint, pDb, (endpoint, db) =>
endpoint.getStuff().then(withThis(db, db.writeStuff)));
}); If A safe option is to either only accept a single promise: readConfig().then(config =>
using(apiEndpoint(config.url), endpoint => using(createDbConnection(config.db), db =>
endpoint.getStuff().then(withThis(db, db.writeStuff))))); or accept multiple functions that return promises: readConfig().then(config =>
using(_=> apiEndpoint(config.url), _=> createDbConnection(config.db), (endpoint, db) =>
endpoint.getStuff().then(withThis(db, db.writeStuff)))); (or for convenience, accept both and make it polymorphic? :D) and since using will be responsible for invoking those functions, it can handle the case where one of them throws and clean up the previously allocated resources. |
In practice it would almost always be a promisified callback function ( |
And then you build a generic database lib with promises, which does function getConnection(url) {
var opt = parse(url);
// adapters are promisified
var adapter = getAdapter(opt.databaseType);
return adapter.getConnectionAsync();
} If you forget to wrap this with something like |
Yes but that function returns a promise while not making sure it will always reject the promise should exception happen. So the function has a very critical bug. You cannot use that function well in any case, not just when using Even the dispose method could do something to break contract like calling |
I think this mistake is far more common than Still, it would be nice if functions are also accepted. That way, if you don't know whether the resource allocator of that library follows the convention or not, you could just wrap it with an arrow lambda. |
I don't think that warning sign is specific to |
related. C# is exactly the same as python:
Is exactly the same as:
The problem in our use case is that we still want r1 and r2 to be run in parallel. Or do we? |
@ralt They can and will be acquired in parallel. If any fails, the ones that were acquired will be disposed and the block not run and the returned promise will be rejected. |
@petkaantonov it may be easier to instead go in a recursive manner, like C# is doing. This way we don't have any leaking issue. It also means that the resources are not fetched in parallel. Overall, it simplifies the issue a lot. |
This makes I need it to do 2 things:
|
Also, some more use cases:
Adding, this is worth a read: http://stackoverflow.com/questions/21118201/can-using-with-more-than-one-resource-cause-a-resource-leak |
Hey, I wrote my prototype variant here. :) Needs some more tests, but let me know what you think. |
I'm in a test week - promise to write unit tests later :) |
Eco system support just isn't there and for othercases it's just a finally that's flipped around. |
Please keep open for discussion :)
|
You can discuss even if it's closed :P |
Just look at what happened with Peomise.sequence - so many dupes :p |
A strong use case here? http://stackoverflow.com/a/22543294/995876 |
Yes, a strong case. One of many. |
The SO post is mine and I find this discussion interesting. I think one thing that is important to cover here is that some resources are finite and others are not. The nondeterministic nature of cleanup by the garbage collector or a straight-up leak in the case of something like a pooled resource is what I think you are trying to cover here. The other kind of resource could be something like a JSON object or string of HTML from a web request, or -- like in my case -- the contents of a file. Often a number of resources that can be allocated in parallel are brought together to do some operation, some of these resources are finite while others are not. In my case the db connection is finite while the string of the SQL script in memory is not (not strictly by my definition above anyway). So the dichotomy with the using statement in C# and what you are trying to do here is that only finite resources are meant to be used in the using statement and, like was mentioned above, they are allocated serially, while I think the true value of this function would be in the parallel allocations without having to do |
I've been spending some time thinking about this and, to solve the problems of parallel resolution and deterministic finalization in the scope of chainable promises, we need to separate the trigger for disposing of a finite resource from the allocation and consumption. This in combination with a simple helper to wrap checking for a fulfilled promise would make it fairly obvious what is going on and also get rid of the closures required in the solution in the case of my problem posted on SO. function execFile(db, file) {
console.log('Connecting to ' + db);
return Promise.props({
client: connect('postgres://' + credentials + host + '/' + db),
initSql: fs.readFileAsync(file, 'utf8')
}, true)
// The true tells 'props' to call 'binds' with this object,
// maybe make a 'propBind' or empty 'bind' that binds to the prop?
.spread(function(res) {
console.log('Connected to ' + db);
console.log('Running init script ' + file);
return res.client.queryAsync(res.initSql);
}).finally(function() {
this.client.ifFulfilled(function(x) { x.end(); });
// The callback gets the value of the promise,
// or it could bind 'this' in the callback to the value of the promise;
// though I'm not familiar with the performance repercussions.
});
} This unfortunately does not help in the case of forgetting the finally handler. One thing that could help in that respect is a weak reference promise that will call a destructor when it is garbage collected. However, we don't have finalizers, and without some sort of hook we can't really catch the case where an object is holding a finite resource at time of garbage collection; any solution that honors chaining is going to necessitate some type of 'call' to notify the resource that it can be safely released. Something possibly like TooTallNate/node-weak could help on Node, but we would need shims on the browser side... if they exist. Something more explicit and similar to this using idea could work, but I am not familiar enough with the codebase to know if this would be feasible to implement: function execFile(db, file) {
console.log('Connecting to ' + db);
return Promise.all([
// Wraps the promise returned by connect in a DisposablePromise
// providing a disposer
Promise.disposable(connect('postgres://' + credentials + host + '/' + db), function(x) { x.end(); }),
fs.readFileAsync(file, 'utf8')
]).spread(function(res) {
console.log('Connected to ' + db);
console.log('Running init script ' + file);
return res.client.queryAsync(res.initSql);
}).dispose();
// Triggers the disposer of any promises in the chain,
// before this point, that were wrapped in a DisposablePromise
} Again, it is still something that needs to remember to be called and, again, I don't know if something like this is even possible with chaining outside the method where this promise chain is returned or other possible use-cases. Just kind of thinking out loud here. |
@joshperry what are your thoughts on https://github.com/spion/promise-using ? |
@joshperry the proposed function would be parallel and handle cleanups with a convenient syntax, e.g. you could have done in your SO problem like so: function execFile(db, file) {
console.log('Connecting to ' + db);
return using(connect('postgres://' + credentials + host + '/' + db),
fs.readFileAsync(file, 'utf8'),
function(client, initSql) {
console.log('Connected to ' + db);
console.log('Running init script ' + file);
return client.queryAsync(initSql);
});
} The disposer that knows to call .end on dbclients and do nothing for strings would have been defined elsewhere with |
@spion I like the idea better though that one could just say |
I like how simple the disposer With that the code could now be (without any outside set up assumptions): function execFile(db, file) {
return using(
connect('postgres://' + credentials + host + '/' + db).disposer("end"),
fs.readFileAsync(file, 'utf8'), function(client, initSql) {
return client.queryAsync(initSql);
});
} Of course in practice you would just define |
@spion @petkaantonov This solution handles all my needs and I think is in-line with the reason we need promises and the need for deterministically handling the lifetime of finite resources. |
@joshperry in the promise-using branch |
@petkaantonov this is for 2.0 right? |
Yeah 2.0 still needs some major documentation changes then it's good to go |
2.0 is done in the iterators branch, not using branch |
Awesome, looking forward to using more using :) On Wed, Apr 23, 2014 at 12:57 PM, Petka Antonov [email protected]:
|
After having this discussion on IRC, making this a little more official.
Having a method allowing us to handle resource lifecycles is a pretty good idea, for any application communicating with a database for example.
It's basically the same concept as C#'s using or Java's try.
Here is the proposed syntax:
A naive (i.e. not fully working) implementation of this would be:
This means that we require the
resource
object to implement aclose
method, that will take care of freeing the resource. It's the same concept as Java requiring an AutoCloseable interface. The method can bedispose
or whatever.Thoughts?
The text was updated successfully, but these errors were encountered: