-
Notifications
You must be signed in to change notification settings - Fork 395
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 convenience methods like .always() #13
Comments
an .addErrback() or .fail() (see eg. http://api.jquery.com/deferred.fail/) could be a nice addition. there is nothing you couldn't do with .then(), but i use it all the time to make code more readable. |
oh and +1 for .always() |
Hey @sifu, thanks for the feedback. I do have a concern about .addErrback()/.fail(), and maybe it applies to .always() as well. Do you think there will be confusion between Promises/A semantics and jQuery Deferred semantics? For example: var success, failed;
success = failed = function(val) {
return val + 1;
};
function log(val) {
console.log(val);
}
// First, jQuery Deferred
var d = $.Deferred();
// This prints 1 because jQuery Deferred isn't Promises/A
// then(), fail(), etc. return `this`, and all handlers receive 1
// no matter where in the "chain" they fall
d.then(success).fail(failed).fail(log);
d.reject(1);
// Now when.js
// Suppose when.js's promises support .fail() - would be similar for always()
d = when.defer();
// This prints 2 because of Promises/A forwarding
// then(), fail() return a new Promise that receives the result
// of the previous Promise
d.then(success).fail(failed).fail(log);
d.reject(1); So, in the second example, some may feel that it reads as if you are registering the success and failed handlers with the same promise, but by Promises/A semantics, you really aren't. The deferred is rejected with the value Maybe it's just a documentation issue, but I do want to be very careful here not to introduce any APIs that might cause a lot of confusion (and possibly have to rip them out later!) Thoughts? |
i think i agree with your concerns. my problem is, that i'm so used to the original deferreds API, because i use deferreds since about a decade (with twisted-python) and promises since about 1.5-2 years, that my thoughts on that matter are very biased. btw. always thought the deferred methods .addCallback, .addErrback, addBoth, ... are way clearer than the promised equivalents like .then and all the rest. i also think i withdraw my "+1" for .always( ) :) it is a shame that jQuery opted for a non-standards API. since jQuery is so common, their API will be the one most javascript programmers will get familiar with. :( |
Thanks, really appreciate all the feedback so far, @sifu. This is a tough issue, for sure--trying to provide convenience while avoiding confusion, and I totally agree that jQuery's choices (and popularity) make it even tougher. The really unfortunate thing about jQuery Deferred, IMHO, is that they chose to look like Promises/A, but not act like it, i.e. I think we need more discussion/thinking on this issue before we know what is best. I'll keep the issue open until we decide something, but we probably won't implement anything any time soon. |
btw. i don't know if you know the reasoning behing jquerys .pipe() vs .then(): they had concerns, that always returning a new promise from .then() would be a performance problem. and they talked to the dojo guys about how to resolve that problem and figured that a new method .pipe() would be the best solution. thats also the reason, why they introduced this method one version later. imho a better way would have been to do it the other way around. provide a method, that doesn't return a new promise (which you can opt in to use if you have performance issues), but implement .then() like it is supposed to be. |
I had heard that once before, but can't remember where. Pretty interesting. I know jQuery has their own goals and priorities, so I can certainly appreciate that performance might be a top priority. But, yeah, I do agree that "doing it the other way around" may have been a better approach, at least from the perspective of being friendly to Promises/A. I think (hope) the fact that when.js assimilates jQuery Deferred now gives jQuery devs a good option if they want Promises/A. They can use $.Deferred if they want, but then can call We've been using when.js as a part of wire.js (https://github.com/briancavalier/wire), and also in application-level code, on a big project (300+ AMD modules) for the past year, and we haven't yet had any perf problems that we could attribute to promises. I don't have any hard numbers, but I feel like the performance is at least good enough that application code will probably be more of a perf hit than promises. |
I think I'm back to feeling like when(computeResult(),
handleResult,
handleFailure); In this case, if var result;
try {
result = computeResult();
} catch (e) {
handleFailure(e);
}
handleSuccess(result); So, if when.js provides a when(computeResult(),
handleResult)
.fail(handleFailure); In that case, because of forwarding, when(computeResult(),
handleResult)
.then(null, handleFailure); I'm still torn, but it seems like the right thing to do is to pick the scenarios that are most likely to cause headaches, and provide nice ways of dealing with those, and then let documentation handle other, less-likely scenarios (like Any thoughts? |
huch, i just realize, that i never thought much about, if handleFailure gets called in case handleResult has an error (when you add both to .then at once). i almost never attach both, and always have a single error handler at the bottom of my promise chain. so a .fail() would make more people use success and error handler separately and therefore avoid the issue. and i can't recall a single scenario, where i wanted to construct something to the liking of .fail().fail(). i again have to say, that i preferred .addCallback .addErrback, back in the twisted-python days :) why didn't promises continued to use this naming convention... :/ btw 300+ modules sound big. wow! what does the app do? |
Coincidentally, I just got tripped up by this, and had to end up writing some code that looks like: when(doStuff(), doOtherStuff /*, cleanup */) // don't call cleanup here, since doOtherStuff may fail!
.then(cleanup, cleanup); It'd sure would have been nice to write that like this: when(doStuff(), doOtherStuff)
.always(cleanup); I'm curious about addCallback and addErrback. Do you feel like they are more intuitive solely because of their names, or because they promote writing code like: promise.addCallback(handleSuccess);
promise.addErrback(handleFailed); Do Twisted's Deferreds/Promises support chaining and forwarding? If you have pointers to some good examples or tutorials, I'd love to take a look and get a feel for how Twisted's Deferreds work. The 300+ module app was one that @unscriptable and I worked on together starting back in November 2010. The company is LifeIMAGE, and they do cloud-based medical image sharing. The app was their front-end for uploading, viewing, managing, and securely sharing medical images in a workflow across doctors, hospitals, etc. It was highly visual and interactive, so most of the modules were Views that had sub-modules for their HTML template, CSS, JS view controller, and i18n. Cool stuff, and a really fun project. |
i prefere addCallback / addErrback because of both, and twisteds deferreds work differently to promises in that they don't return a new deferred/promise like .then does, but the state is passed on to the next callback/errback, so the effect is similar and easier. i don't use twisted anymore, but i think http://twistedmatrix.com/documents/current/core/howto/defer.html is still the best explanation. |
Very interesting, and thanks for the link--I'll definitely take a look. |
Very good read about twisted deferreds! Thanks again for the link. I'm putting this back on the table for 1.1.0, now that I think the benefits outweigh the drawbacks, and the use cases where |
@sifu any thoughts on when(stuffHappens, handleIt).or(recoverFromFailure);
// vs.
when(stuffHappens, handleIt).fail(recoverFromFailure); To me, |
i'm not a english native speaker, so i don't necessary associate the same things to words. |
I agree - How about From a fluent API perspective, I think it reads very well:
which matches the way you earlier described the behavior in plain English. |
Cool. Yeah, when(stuffHappens, handleIt).fails(recoverFromFailure);
// vs.
when(stuffHappens, handleIt).or(recoverFromFailure); I like both of those. And here's an example using // awkward or ok?
promise.then(handleIt).fails(recoverFromFailure);
// vs.
promise.then(handleIt).or(recoverFromFailure); Hard for me to say which or those I prefer right now ... have to stare at them for a while :) Any more thoughts after seeing those two examples? |
Here are the same examples using @sifu's when(stuffHappens, handleIt).onFailure(recoverFromFailure); and using promise.then(handleIt).onFailure(recoverFromFailure); |
Just to chime in on the naming, I think
|
Ah, very interesting! It reads nicely, and seems intuitive. I'd love to hear what other folks think of Thanks for the suggestion, @jonnyreeves |
+1 for "otherwise" |
+1 for "otherwise" - I am inclined to mirror that API method name in Deft JS's promises |
Since |
|
Over in the Deft JS project, we also adopted Today, I had a developer report an issue with In our discussions here and elsewhere, we considered Consider: var promise = check();
promise.then( proceed );
promise.otherwise( recover ).then( proceed ); The developer in question was surprised that The issue is that So this raises some questions.
|
Hey John, that's certainly an interesting case of perceptions! I've not run into any confusion with otherwise() yet, although I do agree that the .otherwise().then() construct is a strange one. Seeing .then(proceed) twice in the same promise chain, so close to one another was def a red flag for me. Interestingly, if I understand your example correctly, the last line is sufficient by itself: check().otherwise(recover).then(proceed); To my eyes, that is very readable and obvious, but I also know that I look at promises every day, and so I'm not always the best judge of what is intuitive to other folks! Do you think this could be a learning curve and/or documentation issue, rather than something intrinsically misleading about otherwise()? Another thing to consider is that although otherwise() isn't a standard Promises/A method, there are now at least 3 implementations that use it (when.js, DeftJS, and the upcoming dojo 1.8 release), and they all currently behave in the same way. That kind of consistency should help with general learning curve a bit as promises become more widely used. As for introducing control flow, I think it would be interesting to explore. I'm not sure it belongs in the promise implementation itself, but rather feels to me like something that should be built on top of promises. If it's integrated into the promise implementation, I think it would have to be done very carefully so as not to break Promises/A interoperability. Of course, if your promises are never exposed in a way that they might be consumed by code that is expecting Promises/A, interoperability is less of an issue. For when.js, tho, that's not an option, since many folks are using it alongside other promise implementations. So, I guess to sum up: 1) right now I wonder if this is a documentation or learning curve issue, and 2) I think promise-based control flow constructs could be very useful, but it feels like something higher level that should be built on top of promises. Thoughts? |
Hello everybody! I'm a developer John is taking about... :)
For me it seems expressive - the chain describes the full control flow I use for loading init data:
The app tries to init and asks for login if not authorized. App reignites after login. If we have an active session already we go the first 'then' and login does not appear. |
Hey Nikolai! Promises, and specifically Promises/A, aren't really intended, by themselves, as a tool for setting up and running complex control flow graphs. They're really intended to manage future values, and observers of those values. That said, it's entirely possible to build more complex asynchronous control flow on top of promises. I built an asynchronous state machine on top of when.js promises, and a simple polling utility
check().otherwise(recover).then(proceed);
check().then(null, recover).then(proceed); They are both analagous to the following synchronous code: var value;
try {
value = check();
} catch(e) {
value = recover(e);
}
proceed(value); So, while However, I think it's possible to accomplish your example without layering any additional control flow on top of promises: init()
.otherwise(function() {
// If init() rejects, we need to recover before proceeding
// This inner function represents a sub-flow, and can also
// return a promise. Subsequent *outer* callbacks (see proceed below)
// will only be invoked after the returned promise resolves.
return login()
.then(init);
})
.then(proceed); It's possible to reduce the nesting a bit by abstracting the recovery: init()
.otherwise(recover)
.then(proceed);
function recover() {
return login()
.then(init);
} Both of those basically say "try to initialize, and if that fails, recover by logging in and then initializing. Once successfully initialized, proceed". Again, it is equivalent to the synchronous That still may be slightly more nesting that you'd like, and if your control flow graph has lots of additional branches and sub-flows, it could become a bit more unweildy. So, depending on your needs, it may be worth building simple simple promise-based control flow constructs. I hope that helps, and feel free to keep the discussion going if you have more questions. |
Hello Brian! Thank you for your explanation! |
No problem, Nikolai, glad you found a solution, and thanks @johnyanarella for bringing this discussion up. Robo is definitely a work in progress, so the usual caveats apply :) If you try it out, though, I'd certainly love to hear what you think. |
Great responses, @briancavalier. Your views on this mirror mine - and to the extent that there's a learning curve associated with the proper application of the |
Currently, when.js's promise provides
.then()
, so if you want to register the same callback to run whether the promise is resolved or rejected, you have to pass it explicitly as both thecallback
anderrback
towhen()
or.then()
. Both jQuery Deferred, and Q's promises provide convenience methods for this.Here's how you have to do it with when.js currently:
That's not horrible, but this seems like a nice convenience:
It might be interesting to provide both
promise.always(callback)
andwhen.always(promise, callback)
so thatwhen.always()
can be used with other promise implementations. Also, since something likepromise.always()
is not part of the Promises/A spec, relying on it would mean tying your code to when.js's promise implementation. Usingwhen.always(promise)
is a bit more decoupled.Seems like this also opens the door for a method that registers only a rejection handler. For example, to do that now:
Which is kind of ugly, and might cause a "WTF". So, could provide something like:
This seems like a much more limited use case to me, though.
The text was updated successfully, but these errors were encountered: