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

Remove single-promiseable exception in jQuery.when #2018

Closed
gibson042 opened this Issue Jan 19, 2015 · 10 comments

Comments

Projects
None yet
5 participants
@gibson042
Member

gibson042 commented Jan 19, 2015

From http://api.jquery.com/jQuery.when:

If a single Deferred is passed to jQuery.when(), its Promise object (a subset of the Deferred methods) is returned

For consistency, we should always create a new master Deferred.

We follow through, but the code—both ours and library consumers—is more complicated than it needs to be as a result. The upcoming major version bump is a chance to unify single- and multiple-argument calls to jQuery.when, if we agree that doing so is valuable. (we did not)

@gibson042 gibson042 added this to the 3.0.0 milestone Jan 19, 2015

@dmethvin

This comment has been minimized.

Show comment
Hide comment
@dmethvin

dmethvin Jan 19, 2015

Member

I agree it makes sense to create a new Deferred rather than borrow the first one, so this is good with me as well.

Member

dmethvin commented Jan 19, 2015

I agree it makes sense to create a new Deferred rather than borrow the first one, so this is good with me as well.

@jaubourg

This comment has been minimized.

Show comment
Hide comment
@jaubourg

jaubourg Jan 19, 2015

Member

If I read correctly, this is not just about not reusing the original object. It's about ensuring $.when always resolves as an array.

Currently, $.when is idempotent. Meaning that $.when( $.when( "string" ) ); will resolve as "string" no matter the number of calls. Making it "less complicated than it needs to be" would mean that this would become [ [ "string" ] ] (and on and on). You can then forget about using $.when for handling inputs that may or may not be a Deferred/Promise because you would effectively change (and by change I mean obfuscate/complicate) the resolve value in the process.

The actual solution is to have another utility that would be used for arrays explicitly:

jQuery.whenArray = function( array ) {
    return jQuery.when.call( jQuery, array );
};
Member

jaubourg commented Jan 19, 2015

If I read correctly, this is not just about not reusing the original object. It's about ensuring $.when always resolves as an array.

Currently, $.when is idempotent. Meaning that $.when( $.when( "string" ) ); will resolve as "string" no matter the number of calls. Making it "less complicated than it needs to be" would mean that this would become [ [ "string" ] ] (and on and on). You can then forget about using $.when for handling inputs that may or may not be a Deferred/Promise because you would effectively change (and by change I mean obfuscate/complicate) the resolve value in the process.

The actual solution is to have another utility that would be used for arrays explicitly:

jQuery.whenArray = function( array ) {
    return jQuery.when.call( jQuery, array );
};
@dmethvin

This comment has been minimized.

Show comment
Hide comment
@dmethvin

dmethvin Jan 19, 2015

Member

If we want $.when to accept any kind of thenable or standard Promise, I think we'd want to handle the $.when( thenable ) case and create a new Deferred there. So always creating a new one seems like the best way to go for consistency and predictability.

I think that changing the array case would be a lot more visible and breaking, and a separate API might be the best way to "document" the confusion people have in trying to call $.when with an array.

Member

dmethvin commented Jan 19, 2015

If we want $.when to accept any kind of thenable or standard Promise, I think we'd want to handle the $.when( thenable ) case and create a new Deferred there. So always creating a new one seems like the best way to go for consistency and predictability.

I think that changing the array case would be a lot more visible and breaking, and a separate API might be the best way to "document" the confusion people have in trying to call $.when with an array.

@jaubourg

This comment has been minimized.

Show comment
Hide comment
@jaubourg

jaubourg Jan 19, 2015

Member

If we want $.when to accept any kind of thenable or standard Promise, I think we'd want to handle the $.when( thenable ) case and create a new Deferred there. So always creating a new one seems like the best way to go for consistency and predictability.

You're perfectly right.

Member

jaubourg commented Jan 19, 2015

If we want $.when to accept any kind of thenable or standard Promise, I think we'd want to handle the $.when( thenable ) case and create a new Deferred there. So always creating a new one seems like the best way to go for consistency and predictability.

You're perfectly right.

@gibson042

This comment has been minimized.

Show comment
Hide comment
@gibson042

gibson042 Jan 19, 2015

Member

This is in fact a double request; shame on me. Sounds like we're in agreement in always creating a new Deferred, so 👍 there.

The other bit, correctly identified by @jaubourg, is me going off half-cocked about yet another inconsistency in our API.

Currently, $.when is idempotent

For single arguments. The multiple-argument case behaves entirely differently, and it's that incongruity that's chafing me at the moment.

$.when( $.when( "string" ) ); will resolve as "string" no matter the number of calls. Making it "less complicated than it needs to be" would mean that this would become [ [ "string" ] ] (and on and on). You can then forget about using $.when for handling inputs that may or may not be a Deferred/Promise because you would effectively change (and by change I mean obfuscate/complicate) the resolve value in the process.

Let's stop mixing that cast operation with an "all" operation. I don't even know which is the primary function of $.when, though this ticket implicitly assumes the latter and your suggestion ("have another utility that would be used for arrays explicitly") implicitly assumes the former. Maybe we can't even stop now, but we could certainly pave the way by providing distinct functions (e.g., jQuery.Deferred.cast and jQuery.Deferred.all).

Member

gibson042 commented Jan 19, 2015

This is in fact a double request; shame on me. Sounds like we're in agreement in always creating a new Deferred, so 👍 there.

The other bit, correctly identified by @jaubourg, is me going off half-cocked about yet another inconsistency in our API.

Currently, $.when is idempotent

For single arguments. The multiple-argument case behaves entirely differently, and it's that incongruity that's chafing me at the moment.

$.when( $.when( "string" ) ); will resolve as "string" no matter the number of calls. Making it "less complicated than it needs to be" would mean that this would become [ [ "string" ] ] (and on and on). You can then forget about using $.when for handling inputs that may or may not be a Deferred/Promise because you would effectively change (and by change I mean obfuscate/complicate) the resolve value in the process.

Let's stop mixing that cast operation with an "all" operation. I don't even know which is the primary function of $.when, though this ticket implicitly assumes the latter and your suggestion ("have another utility that would be used for arrays explicitly") implicitly assumes the former. Maybe we can't even stop now, but we could certainly pave the way by providing distinct functions (e.g., jQuery.Deferred.cast and jQuery.Deferred.all).

@jaubourg

This comment has been minimized.

Show comment
Hide comment
@jaubourg

jaubourg Jan 19, 2015

Member

Currently, $.when is idempotent

For single arguments. The multiple-argument case behaves entirely differently, and it's that incongruity that's chafing me at the moment.

From wikipedia:

Idempotence is the property of certain operations in mathematics and computer science, that can be applied multiple times without changing the result beyond the initial application.

Case in point:

var output = $.when( 1, 2, 3 );

output.done( function( a, b, c ) {
    a === 1 && b === 2 && c === 3;
} );

$.when( output ).done( function( a, b, c ) {
    a === 1 && b === 2 && c === 3;
} );

That's pretty consistent if you ask me. If it wasn't idempotent, then you would have the following:

$.when( output ).done( function( a, b, c ) {
    a === [ 1, 2, 3 ]  && b === undefined && c === undefined;
} );

I don't think this is desirable.

Now, a lot of people want to toy with arrays of Deferreds and apply is confusing to them, I'd say, let's create $.whenArray or whatever you wanna call it.

Let's stop mixing that cast operation with an "all" operation. I don't even know which is the primary function of $.when

Arguments for $.when are just treated exactly like returned values from a .then() handler:

$.when( "string" ).done( function( string ) {
    string === "string";
} );

// mirrors

resolvedDeferred.then( function() {
    return "string";
} ).done( function( string ) {
    string === "string";
} );

// AND

var deferred = Deferred().resolve( "string" );

$.when( deferred ).done( function( string ) {
    string === "string";
} );

// mirrors

var deferred = Deferred().resolve( "string" );

resolvedDeferred.then( function() {
    return deferred;
} ).done( function( string ) {
    string === "string";
} );

In fact, and in a manner of speaking, $.when behaves like a $.then() handler that would return multiple values (and hence give multiple resolution arguments).

I don't really get the confusion here. It's all very consistent.

Member

jaubourg commented Jan 19, 2015

Currently, $.when is idempotent

For single arguments. The multiple-argument case behaves entirely differently, and it's that incongruity that's chafing me at the moment.

From wikipedia:

Idempotence is the property of certain operations in mathematics and computer science, that can be applied multiple times without changing the result beyond the initial application.

Case in point:

var output = $.when( 1, 2, 3 );

output.done( function( a, b, c ) {
    a === 1 && b === 2 && c === 3;
} );

$.when( output ).done( function( a, b, c ) {
    a === 1 && b === 2 && c === 3;
} );

That's pretty consistent if you ask me. If it wasn't idempotent, then you would have the following:

$.when( output ).done( function( a, b, c ) {
    a === [ 1, 2, 3 ]  && b === undefined && c === undefined;
} );

I don't think this is desirable.

Now, a lot of people want to toy with arrays of Deferreds and apply is confusing to them, I'd say, let's create $.whenArray or whatever you wanna call it.

Let's stop mixing that cast operation with an "all" operation. I don't even know which is the primary function of $.when

Arguments for $.when are just treated exactly like returned values from a .then() handler:

$.when( "string" ).done( function( string ) {
    string === "string";
} );

// mirrors

resolvedDeferred.then( function() {
    return "string";
} ).done( function( string ) {
    string === "string";
} );

// AND

var deferred = Deferred().resolve( "string" );

$.when( deferred ).done( function( string ) {
    string === "string";
} );

// mirrors

var deferred = Deferred().resolve( "string" );

resolvedDeferred.then( function() {
    return deferred;
} ).done( function( string ) {
    string === "string";
} );

In fact, and in a manner of speaking, $.when behaves like a $.then() handler that would return multiple values (and hence give multiple resolution arguments).

I don't really get the confusion here. It's all very consistent.

@gibson042

This comment has been minimized.

Show comment
Hide comment
@gibson042

gibson042 Jan 19, 2015

Member

I'm not going to die on this hill. We just resolved to keep the current interfaces ($.when( singleArg ) is analogous to Promise.cast( singleArg ) and $.when( arg1, arg2, ... ) is analogous to Promise.all([ arg1, arg2, ... ])), and I'm about to update the description here so it covers only eliminating reuse of a single input Deferred.

Member

gibson042 commented Jan 19, 2015

I'm not going to die on this hill. We just resolved to keep the current interfaces ($.when( singleArg ) is analogous to Promise.cast( singleArg ) and $.when( arg1, arg2, ... ) is analogous to Promise.all([ arg1, arg2, ... ])), and I'm about to update the description here so it covers only eliminating reuse of a single input Deferred.

@mgol

This comment has been minimized.

Show comment
Hide comment
@mgol

mgol Aug 19, 2015

Member

I assume this issue is not only about making sure jQuery.when(deferred.promise()) always creating a new Deferred but also about jQuery.when(thenable) not treating thenable as a regular value?

In other words, this: http://jsfiddle.net/0r62we79/9/ should print 1 after 2 seconds, not immediately, as this: http://jsfiddle.net/0r62we79/11/ does.

Member

mgol commented Aug 19, 2015

I assume this issue is not only about making sure jQuery.when(deferred.promise()) always creating a new Deferred but also about jQuery.when(thenable) not treating thenable as a regular value?

In other words, this: http://jsfiddle.net/0r62we79/9/ should print 1 after 2 seconds, not immediately, as this: http://jsfiddle.net/0r62we79/11/ does.

@gibson042

This comment has been minimized.

Show comment
Hide comment
@gibson042

gibson042 Aug 19, 2015

Member

That's actually a separate issue, and a blocker at that: #2546

Fortunately, it's easy enough to fix, though. Thanks for the report.

Member

gibson042 commented Aug 19, 2015

That's actually a separate issue, and a blocker at that: #2546

Fortunately, it's easy enough to fix, though. Thanks for the report.

@dmethvin dmethvin self-assigned this Sep 25, 2015

@dmethvin

This comment has been minimized.

Show comment
Hide comment
@dmethvin

dmethvin Sep 25, 2015

Member

Looking at both the code and docs for jQuery.when() I see a lot of undocumented behavior around progress notification. If we were to drop that in 3.0 it seems like we could greatly simplify things and just treat all args as thenables regardless of their source.

We could still have (and document!) a notify for the master Deferred that would fire as each subordinate is resolved, if that's desirable. Curiously, that isn't done today, the notifies are just passed through from the subordinates which seems less useful. I'd be fine with just dropping notifications entirely though, especially since there are no docs.

Member

dmethvin commented Sep 25, 2015

Looking at both the code and docs for jQuery.when() I see a lot of undocumented behavior around progress notification. If we were to drop that in 3.0 it seems like we could greatly simplify things and just treat all args as thenables regardless of their source.

We could still have (and document!) a notify for the master Deferred that would fire as each subordinate is resolved, if that's desirable. Curiously, that isn't done today, the notifies are just passed through from the subordinates which seems less useful. I'd be fine with just dropping notifications entirely though, especially since there are no docs.

timmywil added a commit to timmywil/jquery that referenced this issue Nov 11, 2015

timmywil added a commit to timmywil/jquery that referenced this issue Nov 11, 2015

@timmywil timmywil assigned timmywil and unassigned dmethvin Nov 11, 2015

timmywil added a commit to timmywil/jquery that referenced this issue Nov 11, 2015

timmywil added a commit to timmywil/jquery that referenced this issue Nov 13, 2015

@timmywil timmywil closed this in #2707 Nov 13, 2015

@lock lock bot locked as resolved and limited conversation to collaborators Jun 19, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.