How to call deferred functions sequentially?

2019-02-20 02:33发布

问题:

My code:

<?php
if(isset($_GET['m'])) {
    $m = $_GET['m'];
    sleep($m);
    print "done, m=$m";
    die;
}
?>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js" ></script>
<script>

    function w(s) {
        document.body.innerHTML = document.body.innerHTML+ "<br>" + s
    }

    function aaa(def) {
        w("begin aaa");
        $.ajax({
            type: "GET",
            data: {
                m: 5
            }
        }).done(function(html) {
            w(html);
            def.resolve();
        });
    }


    function bbb(def) {
        w("begin bbb");
        $.ajax({
            type: "GET",
            data: {
                m: 1
            }
        }).done(function(html) {
            w(html);
            def.resolve();
        });
    }

    $(function() {

        $.when(
            $.Deferred(function(d) { aaa(d) }).promise(),
            $.Deferred(function(d) { bbb(d) }).promise()
        ).done(function() {
            w("OK")
        });
    })
</script>

I'm expecting the second function to wait for the first one to complete, that is, my output should be

begin aaa
   <--pause
done, m=1
begin bbb
   <--pause
done, m=5
OK

Instead I'm getting

begin aaa
begin bbb
   <--pause
done, m=1
   <--pause
done, m=5
OK

I guess I'm misunderstanding something fundamental about how deferreds work - can anyone shed a light?

回答1:

How do you expect them to be executed in order if there is no dependency between them? $.when doesn't have and cannot have an influence on the evaluation of the promise, it just waits until each of the promises is resolved.

Your code is way more complicated then it needs to be. $.ajax already returns a promise which gets resolved when the Ajax response was received, so you can just return it from the functions. If you want to execute them in sequence, you can chain them via .then:

These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks.

So your code simplifies to:

function aaa() {
    w("begin aaa");
    return $.ajax({
        type: "GET",
        data: {
            m: 5
        }
    }).done(w);
}


function bbb() {
    w("begin bbb");
    return $.ajax({
        type: "GET",
        data: {
            m: 1
        }
    }).done(w);
}

aaa().then(bbb).done(function() { w('ok'); });

Here, aaa().then(bbb) creates the dependency you need. It basically means "once aaa's promise is resolved, execute bbb". In addition, .then returns a new promise, which gets resolved when the promise returned by bbb gets resolved, which allows you to execute a function when the promises of both, aaa and bbb are resolved.

Maybe these help you to understand promises (and deferreds) better:

  • Deferred versus promise
  • http://learn.jquery.com/code-organization/deferreds/

Example without $.ajax:

function aaa() {
    var def = new $.Deferred();
    setTimeout(function() {
        def.resolve(21);
    }, 3000);
    return def.promise();
}

function bbb(v) {
    var def = new $.Deferred();
    setTimeout(function() {
        def.resolve(v * 2);
    }, 1000);
    return def.promise();
}

// aaa -> bbb -> console.log
// The value is logged after 3 + 1 seconds
aaa().then(bbb).done(function(v) { console.log(v); }); // 42

// (aaa | bbb) -> console.log
// The value is logged after max(3, 1) seconds and both resolved values are
// passed to the final promise
$.when(aaa(), bbb(5)).done(function(v) { console.log(v); }); // [21, 10]


回答2:

$.when() doesn't impose any order on its arguments - they are effectively an "unordered list".

However, .done(), .fail() and .then() do impose order.

First, simplify things by amending aaa() and bbb() to return the Promise-compatible object returned by the $.ajax().done() chain, as follows :

function aaa() {
    w("begin aaa");
    return $.ajax({
        type: "GET",
        data: { m: 5 }
    }).done(w);//without changing anything, w can be passed rather than called from an anonymous function.
}

function bbb() {
    w("begin bbb");
    return $.ajax({
        type: "GET",
        data: { m: 1 }
    }).done(w);//without changing anything, w can be passed rather than called from an anonymous function.
}

Now, to get the effect you seek, use .then() to sequence aaa and bbb :

$(function() {
    aaa().then(bbb).done(function() {
        w("OK");
    });
});

I agree it's slightly confusing here that aaa is executed and bbb is passed, but this is a very common pattern that you will encounter again and again. Eventually it will make sense.

To put aaa and bbb on par with each other, you could write something like :

$(function() {
    $.Deferred().resolve().then(aaa).then(bbb).done(function() {
        w("OK");
    });
});