1

tl;dr : I'm looking for a way to have the first .then callback make changes to the data that is passed to subsequent chained events.

I have a library that encapsulates some async operations.

dudetools.getDude(2); // causes an XHR against REST resource "Dude" for row id 2

For awesomeness purposes, dudetools.getDude returns the promise created by the underlying $.ajax call. Thus, I can do things like:

dudetools.getDude(dudeId).done(function(dudeData) { /* do stuff with dude's data */ });

Now I'm trying to modify dudetools so that it'll do some convenient data-massaging on response data before continuing along the promise chain. I want this massage to happen universally, without calling code having to request it or even know about it.

Because the dudetools implementation can't share a closure with all calling code, I'm hoping to leverage the fact that, in JavaScript, non-scalars are always passed by reference rather than by value.

Consider:

var urStuff = {};
function wreck(x) {
    x.isWrecked = 'so wrecked';
}
wreck(urStuff);
// urStuff.isWrecked === 'so wrecked' ^.^

I dare you to try it.

So, I was hoping this would work:

dudetools = {
    'getDude': function(dudeId) {
        return $.ajax('/api/Dude/' + dudeId).then(function(dudeData) {
            // I'm so clever!
            dudeData.isDuplicated = dudeData.isDuped && dudeData.drillDown > 5;
        });
    }
}

Of course, it doesn't work. My clever code is being executed (I've seen it), and it's reaching the right conclusions, but subsequent Deferred events in the chain never see the modifications. I.e.:

$.when(
    dudetools.getDude(dudeId)
).done(function(mysteriouslyUnmodifiedInfo) {
    /* the info passed to this function is mysteriously unmodified! HALP */
});

Any suggestions? Is there a way to accomplish what I'm after?

Also: I'm still kind of new to promises in general, and my grasp of the differences between Deferreds, Promises, and associated constructs is still kind of fuzzy, so I'd very much appreciate your efforts to be clear and explicit when explaining to me how I've ruined everything.

Thanks very much.

EDIT: updated to reflect fact that dudetools.getDude returns a promise, not a Deferred. Because I now (mostly) understand the difference.

Tom
  • 8,509
  • 7
  • 49
  • 78

2 Answers2

1

The magic of .then is that it pipes its return value into the next callbacks param.

If you don't return your object (even if you haven't changed anything), then undefined is returned by default.

do_something()
 .then(function (json) { return JSON.parse(json); })
 .then(function (response) { return response.data; })
 .then(function (data) { data.tweaked = true; return data; });
Norguard
  • 26,167
  • 5
  • 41
  • 49
0

You'll want to return your own new Deferred.promise() object. http://api.jquery.com/deferred.promise/

dudetools = {
    'getDude': function(dudeId) {
        var dfd = new jQuery.Deferred();
        $.ajax('/api/Dude/' + dudeId).then(function(dudeData) {
            dudeData.isDuplicated = dudeData.isDuped && dudeData.drillDown > 5;
             // I'm so clever!
           dfd.resolve(dudeData);
        });

        return dfd.promise();
    }
}

Hope that helps.

Sumit
  • 1,661
  • 1
  • 13
  • 18
  • This may work, but simply returning modified data from a custom `then()` was simpler. Thanks anyway. 8) – Tom Jan 29 '14 at 16:52
  • 1
    Ha! In the end, this was actually the correct solution. While writing unit tests I discovered unacceptable differences between the behavior of two methods, one of which used @Norguard's approach, and one of which had no middleman. Using Sumit's approach, the change is completely transparent, and my unit tests succeed again. Thanks! – Tom Feb 05 '14 at 21:37