Skip to main content

Bug Tracker

Side navigation

#9937 closed enhancement (wontfix)

Opened July 29, 2011 11:31AM UTC

Closed September 21, 2011 12:17AM UTC

Last modified May 03, 2012 07:25AM UTC

Deferreds, promises and jsXHR-s are plain objects.

Reported by: rkatic Owned by: rkatic
Priority: low Milestone: None
Component: ajax Version: 1.6.2
Keywords: Cc:
Blocked by: Blocking:
Description

Deferreds, promises and jsXHR-s, are created using the module pattern, producing plain objects.

jQuery.isPlainObject( jQuery.Deferred() ) === true;
jQuery.isPlainObject( jQuery.Deferred().promise() ) === true;
jQuery.isPlainObject( jQuery.ajax() ) === true;

This also means that on deep extending, those objects will be duplicated like plain objects and arrays.

Probably the easiest way to fix this, is to change a bit object/instance creation replacing obj = {} with obj = new jQuery.noop() and obj = {...} with obj = new jQery.extend({...}). In this way objects are not directly inherited from Object.prototype.

Patch: https://github.com/rkatic/jquery/compare/jquery:master...rkatic:no_plains.

Another possible problem is that instanceof do not work with deferreds.

( jQuery() instanceof jQuery ) === true;
( new jQuery.Event instanceof jQuery.Event ) === true;
( jQuery.Deferred() instanceof jQuery.Deferred ) === false;

To resolve this too, and to simplify true instance creations via module pattern, it could be useful to bring a utility method that in the following patch is called jQuery.alloc.

Patch: https://github.com/rkatic/jquery/compare/jquery:master...instanceof.

Attachments (0)
Change History (16)

Changed July 29, 2011 01:46PM UTC by dmethvin comment:1

owner: → rkatic
status: newpending

I agree with the findings but don't see the need to change it. Are there some cases where the current implementation becomes a problem?

Changed July 29, 2011 04:18PM UTC by rkatic comment:2

status: pendingnew

I suppose the problem can relatively easily arise on creating more sophisticated plugins, where usage of both deferreds/promises and jQuery.isPlainObject are combined. I suppose it's not too hard to imagine such thing (or I should provide an example?).

Regarding the instanceof issue, I suppose you have to decide if that statement should be supported or not.

Changed July 31, 2011 01:24PM UTC by jaubourg comment:3

component: unfiledajax
type: bugenhancement

You'll have no problem with Deferreds and Promises. The promise() method actually uses $.extend as a mean to attach the promise aspect onto a plain object (Deferreds and Promises are just collections of lexically-bound methods), so the behaviour is exactly as expected and changing it here will break stuff.

The jqXHR object is another story though, seeing as it has fields that are updated. So if you were to $.extend( {}, jqXHR ) before the request has completed, only the original object would get its status, statusCode and responseXXX fields updated. However, callbacks attached through the new object would still be given the original jqXHR.

So, using the extended jqXHR to retrieve fields would be a no-go but that's not really that big of an issue, given callbacks given to the extended object will receive the original jqXHR (and that's the object you should use to retrieve fields).

I dunno. Would the need be widespread enough to warrant some kind of "atomic object" registry that $.extend would look into to control if an object can be deep extended? I'm really no sure myself.

Changed July 31, 2011 03:54PM UTC by rkatic comment:4

_comment0: Deferred objects are state-less, jsXHR ones are not -- so, as you pointed, former objects can be duplicated (deep extend) with no issues. \ \ However, even if deferreds will remain state-less forever, $.isPlainObject itself is part of the public API, and using it against those objects will give unexpected results. \ \ > the behaviour is exactly as expected and changing it here will break stuff \ No-one is proposing to move methods to Deferred.prototype, if that was your concern. \ \ Even if $.isPlainObject is mainly implemented by me, I always was against it because of similar issues. Now we have it in public API, and we have to deal with it. In case of Deferred, changing docs a bit would probably be enough (!?), but that would not be good enough for jsXHR anyway. \ \ > I dunno. Would the need be widespread enough to warrant some kind of "atomic object" registry that $.extend would look into to control if an object can be deep extended? I'm really no sure myself. \ \ Screw the "atomic object" crap - at least, not state-less "instances", should not be plain objects :) \ \ \ 1312155913291775
_comment1: Deferred have no states exposed, jsXHR have -- so, as you pointed, former objects can be duplicated (deep extend) with no issues. \ \ However, even in case Deferred will never expose states in future, $.isPlainObject itself is part of the public API, and using it against those objects will give unexpected results. \ \ > the behaviour is exactly as expected and changing it here will break stuff \ No-one is proposing to move methods to Deferred.prototype, if that was your concern. \ \ Even if $.isPlainObject is mainly implemented by me, I always was against it because of similar issues. Now we have it in public API, and we have to deal with it. In case of Deferred, changing docs a bit would probably be enough (!?), but that would not be good enough for jsXHR anyway. \ \ > I dunno. Would the need be widespread enough to warrant some kind of "atomic object" registry that $.extend would look into to control if an object can be deep extended? I'm really no sure myself. \ \ Screw the "atomic object" crap - at least, "instances" with exposed states should not be plain objects :) \ \ \ 1313487894622271

Deferred have no states exposed, jqXHR have -- so, as you pointed, former objects can be duplicated (deep extend) with no issues, but jqXHR can not.

However, even in case Deferred will never expose states in future, $.isPlainObject itself is part of the public API, and using it against those objects will give unexpected results.

the behaviour is exactly as expected and changing it here will break stuff

No-one is proposing to move methods to Deferred.prototype, if that was your concern.

Even if $.isPlainObject is mainly implemented by me, I always was against it because of similar issues. Now we have it in public API, and we have to deal with it. In case of Deferred, changing docs a bit would probably be enough (!?), but that would not be good enough for jqXHR anyway.

I dunno. Would the need be widespread enough to warrant some kind of "atomic object" registry that $.extend would look into to control if an object can be deep extended? I'm really no sure myself.

Screw the "atomic object" crap - at least, "instances" with exposed states should not be plain objects :)

Changed July 31, 2011 09:58PM UTC by jaubourg comment:5

Deferred are **not** stateless... the state is not exposed that's all. Deferreds and Promises are plain objects and I'm fine with that: they've been purposedly designed that way.

Changed July 31, 2011 11:57PM UTC by rkatic comment:6

_comment0: Yea, for "state-less" I meant that states are not attached to object... I agree that such term was misleading (at least), so I fixed my previous post. \ \ I am not sure if you are aware of what I am proposing. \ My first patch would fix the plain-object issue with minimal changes, with no breaks. Are you disposed to consider a similar change for jsXHR at least?1312256062984661

Yea, for "state-less" I meant that states are not attached to object... I agree that such term was misleading (at least), so I fixed my previous post.

I am not sure if you are aware of what I am proposing.

My first patch would fix the plain-object issue with minimal changes, with no breaks. Are you disposed to consider a similar change for jqXHR at least?

Changed August 16, 2011 05:29AM UTC by addyosmani comment:7

status: newpending

Its a nice fix, but so far, this enhancement still doesn't appear to have a widespread use-case defined (that is generally agreed with) to warrant the change being made.

Changed August 16, 2011 08:38AM UTC by rkatic comment:8

_comment0: Excluding the `instanceof` issue, there are two problems: \ \ 1) `$.isPlainObject( deferred/promise/jqXHR ) === true` \ \ If expected result is `true`, ignore this, but if expected value is `false`, then in my opinion this is more a bug then an enhancement. \ \ 2) `$.extend(true, ...)` will break any copied jqXHR. \ \ Definitely a bug. I don't get the argument that this is not an common use-case. Are we ignoring here that jQuery supports plugins? \ 1313484046972821
_comment1: Excluding the `instanceof` issue, there are two problems: \ \ 1) `$.isPlainObject( deferred/promise/jqXHR ) === true` \ \ If expected result is `true`, ignore this, but if expected value is `false`, then in my opinion this is more a bug then an enhancement. \ \ 2) `$.extend(true, ...)` will break any copied jqXHR. \ \ Definitely a bug. I don't get the argument that this is not an common use-case. Are we ignoring here that jQuery supports plugins and that such usage should work properly even if not present in the jQuery code itself? \ 1313488110051832
status: pendingnew

Excluding the instanceof issue, there are two problems:

1) $.isPlainObject( deferred/promise/jqXHR ) === true

If expected result is true, then ignore this, but if expected value is false, then in my opinion this is more a bug then an enhancement.

2) $.extend(true, ...) will break any copied jqXHR.

Definitely a bug. I don't get the argument that this is not an common use-case. Are we ignoring here that jQuery supports plugins and that such usage should work properly even if not present in the jQuery code itself?

Changed August 18, 2011 04:37PM UTC by dmethvin comment:9

@rkatic, can you describe an application where extending a jqXHR is useful and could not be achieved by other reasonable means?

Changed August 18, 2011 04:37PM UTC by dmethvin comment:10

status: newpending

Changed September 01, 2011 07:56AM UTC by rkatic comment:11

_comment0: @dmethvin: Maybe I was not clear. The point (2) is not about `$.extend(true, {}, jqXHR)`, but about `$.extend(true, {}, {xhr: jqXHR})`. \ \ So the point is not to make jqXHR safe on extending, but to prevent `$.extend(true, ...)` to extend jqXHR.1314863996287160
_comment1: @dmethvin: Maybe I was not clear. The point (2) is not about `$.extend(true, {}, jqXHR)`, but about `$.extend(true, {}, {xhr: jqXHR})`. \ \ So the point is not to make jqXHR safe on extending, but to prevent `$.extend(true, ...)` to copy contained jqXHR.1314864053115748
status: pendingnew

@dmethvin: Maybe I was not clear. The point (2) is not about $.extend(true, {}, jqXHR), but about $.extend(true, {}, {xhr: jqXHR}).

So the point is not to make jqXHR safe on extending, but to prevent $.extend(true, ...) to duplicate contained jqXHR.

Changed September 21, 2011 12:17AM UTC by timmywil comment:12

priority: undecidedlow
resolution: → wontfix
status: newclosed

Scoping is another commonly used tool. The fact that they are not instantiated has its own advantages. Not everything needs to conform to a standard practice of prototyping.

I think we're fine with them being plain objects and I don't see a common use case for the stated extend, even in plugins. Thank you for taking the time to work through this, but I don't think we'll be changing the implementation.

Changed May 02, 2012 10:19PM UTC by katowulf@gmail.com comment:13

I ran into this problem today while trying to create a simple chain of promises. I wrap a series of functions which may or may not return a promise and execute them sequentially.

If the result is a promise, then execution of the next call in the chain is deferred until the promise resolves. Otherwise, the next method is called immediately. If an Error is returned, then the chain of execution is aborted.

Example in jsfiddle: http://jsfiddle.net/katowulf/JCED9/1/

Changed May 02, 2012 10:22PM UTC by anonymous comment:14

To clarify, there is no real method to determine if it's a Deferred or not (I could check 'deferred' in object, but could not another jquery object have a deferred property?

Without instanceof, I'm at a bit of a loss for an effective way to determine if something is a promise/deferred.

Changed May 02, 2012 10:29PM UTC by rwaldron comment:15

A promise is defined as an object that has a function as the value for the property 'then'

http://wiki.commonjs.org/wiki/Promises/A

Changed May 03, 2012 07:25AM UTC by jaubourg comment:16

Replying to [comment:15 rwaldron]:

> A promise is defined as an object that has a function as the value for the property 'then' http://wiki.commonjs.org/wiki/Promises/A

That's the Promise/A way. You can also test for the presence of a promise method and call it to retrieve the promise... that way, you will also catch objects that do not implement the promise interface yet are observable through one (see $.when's code if you're curious).