2

Keeping in mind this statement:

Once the object has entered the resolved or rejected state, it stays in that state. Callbacks can still be added to the resolved or rejected Deferred — they will execute immediately.

from http://api.jquery.com/jQuery.Deferred/

What would you expect this code to produce:

var d = $.Deferred();

var a = function() {
    d.done(function() {
        console.log('inner');
    });
    
    console.log('outer');
};

d.done(a);

d.resolve();

?

I'm expecting it to be inner, then outer. Whereas it's not the case for any jquery version I checked.

Would you consider it as a bug or am I missing the point from the description?

Corresponding JSFiddle: http://jsfiddle.net/U8AGc/

UPD: some background for the question: I expect the a method to behave similarly regardless of how exactly it was invoked: just as an a() or d.done(a)

Community
  • 1
  • 1
zerkms
  • 249,484
  • 69
  • 436
  • 539

2 Answers2

3

I expect the a method to behave similarly regardless of how exactly it was invoked: just as an a() or d.done(a)

No. d.done(a) does not always call a() immediately - most prominently when d is not yet resolved.

Promises/A+ solves this ambiguity by requiring the handlers to be always fired asynchronously, i.e. here you could expect outer to precede inner in every case.

I'm expecting it to be inner, then outer

That is prevented by jQuery.Callbacks, which has an explicit firing flag for not immediately-executing handlers inside handlers; instead the added handlers are appended to the queue. It's a feature to prevent stack overflows as well, and it simplifies the fire function by "locking" it.

Would you consider it as a bug or am I missing the point from the description?

I'd consider not following Promises/A+ the bug :-) The description just didn't handle this - quite uncommon - special case.

I'd say its point was that handlers on settled deferreds are still automatically executed even after the resolution, by "immediately" they didn't necessarily mean "synchronously from within .done" but rather "right away, as soon as possible".

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • "d.done(a) does not always call a() immediately " --- that's what the quoted documentation states. "Callbacks can still be added to the resolved or rejected Deferred — they will execute **immediately**." – zerkms Mar 26 '14 at 01:19
  • Yes, but it doesn't state what happens when you are adding callbacks to a Deferred that *is currently being resolved or rejected* :-) – Bergi Mar 26 '14 at 01:21
  • yep, but it's a fatal difference that must have been mentioned. The point taken though :-) – zerkms Mar 26 '14 at 01:22
  • 2
    "that had been resolved or rejected" --- isn't enough. http://jsfiddle.net/U8AGc/2/ It should probably somehow refer to the fact of nesting them. It's nesting that causes that, not order of execution. – zerkms Mar 26 '14 at 01:27
  • Btw, I cannot see how Promises/A+ resolve the given ambiguity. Could you please point me to a corresponding place? – zerkms Mar 26 '14 at 01:28
  • 1
    Uh, you're completely right. For the non-ambiguity of Promises/A+ I failed to find a good link, but I hope that https://github.com/promises-aplus/promises-spec/issues/68 will give you a start. – Bergi Mar 26 '14 at 02:39
  • it's a *really weird* comment there: https://github.com/promises-aplus/promises-spec/issues/68#issuecomment-13336189 Not sure how one would implement it in javascript without timers. – zerkms Mar 26 '14 at 02:45
  • You wouldn't, you [need some scheduling api](http://promisesaplus.com/#point-67) to ensure the asynchronity. However, [that's a good thing!](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony) – Bergi Mar 26 '14 at 02:52
  • exactly. At this moment it means it will be scheduled with timers (due to lack of another mechanism) and it means that we don't know when exactly the promise will be run, since there may be already a lot of tasks scheduled. I find it really a terrible idea. – zerkms Mar 26 '14 at 02:57
  • Why would you want to know it "exactly"? The handler function is run when its body is evaluated. And notice that "timers" are hardly ever used as promises mostly represent inherently asynchronous tasks which are not finished when the handlers are added, so that it doesn't make a difference to them anyway. – Bergi Mar 26 '14 at 03:06
  • now after reading that thread thoroughly for the second time - I don't have a good explanation for that "exactly" requirement. Probably I'm just used to using jquery's implementation too much, which isn't the best one. – zerkms Mar 26 '14 at 03:27
  • 1
    @zerkms immediate non queued execution of promises leads to race condition and non deterministic code paths (what executes first?) It's a horrible idea. There are very good reasons why the async trampoline requirement is there. Like Bergi said - this is jQuery dropping the ball completely. – Benjamin Gruenbaum Mar 26 '14 at 08:52
  • @Benjamin Gruenbaum: I agree now. If everything is asynchronous - then, okay, I would expect the behaviour I'm having. But I'm still finding the jquery's behaviour inconsistent. – zerkms Mar 26 '14 at 09:54
  • @zerkms that is correct. jQuery's behavior _is_ inconsistent and dangerous. If it wasn't that bad here and in other several cases, and it was slightly faster that would save me including a solid promise library like Bluebird in every clientside project :) – Benjamin Gruenbaum Mar 27 '14 at 14:22
1

Here's what's happening:

var a = function() {
    // the function logging 'inner' is *added* to the call stack
    d.done(function() {
        console.log('inner');
    });
    // 'outer' is logged
    console.log('outer');
};
// `a` has finished executing, then the anonymous function logs 'inner'

I've added a log with the stack trace* in your JSFiddle - you can see that the function a is nowhere to be found in the anon function that's logging inner - jQuery is calling it once a has finished excecuting.

The relevant line in the jQuery source is found here (notice that it's adding to a queue).

*Trace code from here

Community
  • 1
  • 1
SomeKittens
  • 38,868
  • 19
  • 114
  • 143