Skip to content
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

New module to add Y.Deferred, Y.Promise, and Y.when() #36

Closed
wants to merge 10 commits into from
Closed

Conversation

lsmith
Copy link
Owner

@lsmith lsmith commented Sep 4, 2012

Deferred is targeted to serve in the role of transaction layer (second layer in https://gist.github.com/2375130 per https://github.com/yui/yui3/wiki/Ongoing-Development-Discussions).

Feedback welcome while I write tests.

With some real tests, some stubbed tests. Basic functionality
seems to be working, but I haven't been very rigorous about
it yet.
New version uses prototypes and gentleman's privates, and
is broken into two pieces:
1. `deferred` which offers Y.Deferred and Y.Promise with only
   deferred.then(), resolve(), reject(), promise(), and getStatus(),
   and promise.then() and promise().
2. `deferred-extras` which adds deferred/promise.onProgress() and
   deferred.notify() for progress updates, an promise.wait() to
   insert a delay in the chained sequence.

Also added `deferred-when` which creates a `Y.when()` method to wrap
multiple operations and returns a promise that will be resolved when
all individual operations complete.
Split up the code into two modules:
deferred - core implementation
deferred-extras - +onProgress/notify, wait, and Y.when
@module deferred
@since 3.7.0
**/
var slice = [].slice,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.prototype.slice will avoid creating a throwaway array. Nitpicking, I know.

@rgrove
Copy link

rgrove commented Sep 4, 2012

I like it!

@return {Promise} The promise of a new Deferred wrapping the resolution
of either "resolve" or "reject" callback
**/
then: function (callback, errback) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you want it to accept an array of callbacks?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think it necessary. But I'm open to a community vote. An array of callbacks would all initiate from the same point in the event loop, so could cause confusion with the sequential operation of promise.then(fnA).then(fnB) vs the ungauranteed sequence of promise.then([fnA, fnB]) - if fnA is synchronous, they are equivalent, if async, then fnB will complete before fnA (assuming fnB is synchronous or resolves faster than fnA).

I admit, this does complicate the use case of wanting multiple callbacks on the same promise, as it requires breaking the chain. var and = promise.then(fnA); and.then(fnB); and.then(fnC) to get fnB and fnC initiated after fnA.

Maybe this could be resolved with promise.and(callback, errback)? I've seen that in other implementations. Maybe supporting an array of callbacks would be easier. But in either case, I wouldn't consider it a core requirement, since it can be emulated as shown in the broken chain snippet above.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less is more. 👍

@juandopazo
Copy link

Tests look great! Do you want some help writing them?

@nhusher
Copy link

nhusher commented Sep 5, 2012

I can't find the specific source for this claim (I'll keep looking later), but I was under the impression that all promise handlers were executed asynchronously (i.e. wrapped in a setTimeout(fn,0)).

Even if I can't find the specific source, I think it would be a good thing to implement. It improves consistency for asynchronous/synchronous function calls and should have a similar effect to the AsyncQueue module for processing-heavy promise methods.

Aside from that and a few code nitpicks, I think this looks awesome!


results[i] = args.length > 1 ? args : args[0];

if (!--remaining && allDone.getStatus() !== 'rejected') {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This conditional (on line 35) makes me very uncomfortable. I don't think it will pass shifter's jslint, either.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could break the decrement outside the conditional, but figured I'd save a byte or two. Will change for readability.

How dare you suggest my code would cause lint to complain ;) (it didn't, though I was using shifter --lint defaults --strict because of an issue in shifter)

@solmsted
Copy link

solmsted commented Sep 5, 2012

@TeslaNick The CommonJs Promises B proposal requires callbacks to be executed asynchronously. http://wiki.commonjs.org/wiki/Promises/B

This seems to be a fancy implementation of the Promises A proposal which does not have that requirement. http://wiki.commonjs.org/wiki/Promises/A

The Q API is a fancy implementation of Promises B. https://github.com/kriskowal/q
The Q API uses tricks similar to gallery-soon to achieve asynchronicity. http://yuilibrary.com/gallery/show/soon

I agree that it would be nice if callbacks were executed asynchronously but setTimeout is a pretty huge performance waster.

@nhusher
Copy link

nhusher commented Sep 5, 2012

@solmsted Is it? I couldn't find anything that suggested that setTimeout introduced performance overhead and since it's used in semi-real-time code (e.g. animation), I don't think that whatever performance hit it incurs would be a problem for the cases promises are likely to be used in. I think using alternatives (setImmediate and process.nextTick come to mind) when they're available is a reasonable compromise.

@solmsted
Copy link

solmsted commented Sep 5, 2012

@TeslaNick Try this in your favorite browsers: http://jsperf.com/setimmediate-test/5 In many cases, the performance differences are significant.

This is why I made gallery-soon.

In terms of promises, if you have a couple things chained together, unless you're on a mobile device or laptop where the minimum setTimeout timer resolution is increased, you're not likely to notice a difference. As you scale to a Node server performing tons of IO with real-time data and maybe dozens of promises nested within dozens more, the performance issues become a problem.

@lsmith
Copy link
Owner Author

lsmith commented Sep 5, 2012

@solmsted Looks good, though I don't want to bake that much code into this module for ms shaving that will largely be beneficial in long chains of synchronous ops or in Node.js. I'll add a sniff for Y.soon.

It seems like consensus is that all then() callbacks should be async. It looks like jQuery's then() doesn't enforce async. I'm satisfied saying that this implementation is based on Promises/A with deviations.

In reading over the API discussion linked from the Promises/A wiki entry, there was nothing specific about rationale for progress being included in then()'s signature. While I do view jq's implementation as a strong indicator of convention, I'm feeling inclined to dig in my heels on the progress point, and let feedback from implementation-land convince me to recant my heathen ways.

@lsmith
Copy link
Owner Author

lsmith commented Sep 5, 2012

@juandopazo Regarding tests, um, yeah! MOAR TESTS. I'll slam out as many as I can in the next couple hours, then if you want to work from the bottom, we won't collide if working in parallel.

Plus when() now supports passing simple values as well as
functions and promises.  Previously, it would never resolve
the allDone deferred if a simple value was passed.
@juandopazo
Copy link

I like Y.soon, specially if it uses nextTick in Node. I think we'll be seeing more and more APIs go full async in the future. Even Object.observe is being drafted as async, so we'd better get used to it.

* More docs in then()
* errback that doesn't return a failed promise will continue to next callback,
  not next errback.  Signals recovery from the failure.
* Y.when renamed Y.batch
* New Y.when(promiseOrValue, callback, errback) added
* New Y.defer(callback(deferred)) added
* Tests added for new APIs and changed APIs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants