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 comment:1
owner: | → rkatic |
---|---|
status: | new → pending |
Changed July 29, 2011 04:18PM UTC by comment:2
status: | pending → new |
---|
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 comment:3
component: | unfiled → ajax |
---|---|
type: | bug → enhancement |
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 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 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 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 comment:7
status: | new → pending |
---|
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 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: | pending → new |
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 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 comment:10
status: | new → pending |
---|
Changed September 01, 2011 07:56AM UTC by 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: | pending → new |
@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 comment:12
priority: | undecided → low |
---|---|
resolution: | → wontfix |
status: | new → closed |
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 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 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 comment:15
A promise is defined as an object that has a function as the value for the property 'then'
Changed May 03, 2012 07:25AM UTC by 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).
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?