2

I have some functions that return jQuery Deferred objects and I'm having trouble getting my head around chaining them and processing the results.

Consider the following example:

const start = Date.now();

// Print a message with a timestamp.
function log (v) {
    console.log(`${Date.now() - start} ${v}`);
}

// Return a deferred function that resolves 1 second later with 'value'.
function test (value) {
    log(`test(${value})`);
    return $.Deferred(function (def) {
        window.setTimeout(function () {
            log(`resolve(${value})`);
            def.resolve(value);
        }, 1000);
    });
}

// Example:
test(42)
    .then(function (v) { log(v); })
    .then(test(99))
    .then(function (v) { log(v); })
    .then(function () { log('done'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

This is supposed to run test(42), then do something with its results, then run test(99) then do something with those results, all in order. However, it actually outputs (first number is ms since program started):

0 test(42)
0 test(99)
1003 resolve(42)
1003 42
1003 undefined    <-- supposed to be 99
1005 done
1005 resolve(99)

So both of the tests get called at the same time right at the start, and everything else is off. What I want it to output is something like:

0 test(42)
1000 resolve(42)
1000 42
1000 test(99)
2000 resolve(99)
2000 99
2000 done

How can I make this work? I tried returning $.Deferred(...).promise(), with no change in behavior, and I also tried using done instead of then but the only change was it printed 42 a second time instead of undefined.

1 Answer 1

1

Each deferred only resolves once. For each deferred chain, you have to attach them correctly. Also the second call to test needs to be in a function so that it does not execute immediately.

const start = Date.now();

// Print a message with a timestamp.
function log (v) {
    console.log(`${Date.now() - start} ${v}`);
}

// Return a deferred function that resolves 1 second later with 'value'.
function test (value) {
    log(`test(${value})`);
    return $.Deferred(function (def) {
        window.setTimeout(function () {
            log(`resolve(${value})`);
            def.resolve(value);
        }, 1000);
    });
}

// Example:
test(42)
    .then(function (v) { log(v); })
    .then(function () {
        test(99)
           .then(function (v) { log(v); })
           .then(function () { log('done'); });
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Sign up to request clarification or add additional context in comments.

5 Comments

Ah, I see, thanks. And nesting them like this is the only way to do it? So if I'm, say, building a long chain with a for loop or something I have to either write some helper function to do them sequentially, or finesse everything into this nested form?
Once a deferred resolves, it will execute all then() attached to it, regardless of if one contains another deferred or not. So you have to chain your logic with that in mind.
Btw, I did a little experiment playing with your advice, it actually appears to work correctly if I just make the second call to test be a function (that returns that deferred object), even without the nesting, like this. Maybe if the then handler returns something thenable, it changes what the rest of the chain is operating on?
Can you share a link to your experiment?
Huh, interesting.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.