如何避免Javascript / jQuery中的硬编码链式异步函数?

时间:2013-08-09 10:08:48

标签: javascript jquery ajax

我程序中的几乎所有函数都有某种异步调用,但它们都依赖于某些先前函数的结果。因此,我将下一个函数调用硬编码到每个函数中:

function getStuff() {
    $.ajax({
        ...
        success: function(results) {
            // other functions involving results
            getMoreStuff(results);
        }
    });
}

function getMoreStuff(results) {
    $.ajax({
        ...
        success: function(moreResults) {
            // other functions involving moreResults
            doSomethingWithStuff(moreResults);
        }
    );
}

等等。它是一个大型链,每个函数调用下一个函数。虽然这在程序中有效,但它使每个函数都无法单独使用。

我对如何避免这个问题有点失落。我无法弄清楚如何使用通用回调函数,因为当我进行函数调用时,它会像这样结束(使用上面的函数):

getStuff(function() {
    getMoreStuff(results, doSomethingWithStuff);
};

但是“结果”尚未定义。

解决方案似乎很明显,我只是对它有点密集。遗憾!

4 个答案:

答案 0 :(得分:11)

概述

你有几个选择。您可以使用回调函数使用这些函数使用这些函数:

getStuff(function(results) {
    getMoreStuff(results, doSomethingWithStuff);
});

或者像这样,使用jQuery的DeferredPromise对象:

getStuff().then(getMoreStuff).then(doSomethingWithStuff):

使用回调

getStuffgetMoreStuff都接受一个参数,这个参数在完成时会被调用,例如:

function getStuff(callback) {
//                ^------------------------------ callback argument
    $.ajax({
        ...
        success: function(results) {
            // other functions involving results
            callback(results);
//          ^------------------------------------ use the callback arg
        }
    });
}

......和getMoreStuff类似。

使用DeferredPromise

jQuery的ajax功能与其DeferredPromise功能集成在一起。您只需将return添加到现有功能即可实现,例如:

function getStuff(callback) {
    return $.ajax({
        ...
    });
}

(注意:无需success回调。)

然后这段代码:

getStuff().then(getMoreStuff).then(doSomethingWithStuff);

这样做:

  1. getStuff开始ajax来电并返回该来电创建的Promise

  2. ajax调用完成并解析承诺时,将调用getMoreStuff,并将ajax调用的结果作为其第一个参数。它会启动 ajax来电。

  3. getMoreStuff的{​​{1}}来电完成后,调用ajax并调用 的结果(doSomethingWithStuff })。

  4. 使用getMoreStuff而非then非常重要,以便在每个阶段获得正确的结果。 (如果您使用done,则done getMoreStuff都会看到doSomethingWithStuff getStuff来电的结果。)< / p>

    以下是使用ajax的完整示例:

    Fiddle | Alternate Fiddle with the ajax calls taking one second each(让您更容易看到发生了什么)

    ajax

    输出:

    getStuff starting ajax
    
    getMoreStuff got data from first request, starting ajax
    
    doSomethingWithStuff got data from second request

    您不需要使用function getStuff() { display("getStuff starting ajax") return $.ajax({ url: "/echo/json/", type: "POST", data: {json: '{"message": "data from first request"}'}, dataType: "json" }); } function getMoreStuff(results) { display("getMoreStuff got " + results.message + ", starting ajax"); return $.ajax({ url: "/echo/json/", type: "POST", data: {json: '{"message": "data from second request"}'}, dataType: "json" }); } function doSomethingWithStuff(results) { display("doSomethingWithStuff got " + results.message); } getStuff().then(getMoreStuff).then(doSomethingWithStuff); function display(msg) { var p = document.createElement('p'); p.innerHTML = String(msg); document.body.appendChild(p); } 来获益,您可以使用自己的ajaxDeferred对象,这样就可以编写如下链:< / p>

    Promise

    ...适用于您可能有异步完成的任何情况。

    这是一个非one().then(two).then(three); 示例:

    Fiddle

    ajax

    输出:

    one running
    
    one resolving
    
    Two: Got 'one'
    
    two resolving
    
    Three: Got 'two'
    
    three resolving

    必要时可以组合这两个(function one() { var d = new $.Deferred(); display("one running"); setTimeout(function() { display("one resolving"); d.resolve("one"); }, 1000); return d.promise(); } function two(arg) { var d = new $.Deferred(); display("Two: Got '" + arg + "'"); setTimeout(function() { display("two resolving"); d.resolve("two"); }, 500); return d.promise(); } function three(arg) { var d = new $.Deferred(); display("Three: Got '" + arg + "'"); setTimeout(function() { display("three resolving"); d.resolve("three"); }, 500); return d.promise(); } one().then(two).then(three); function display(msg) { var p = document.createElement('p'); p.innerHTML = String(msg); document.body.appendChild(p); } 示例和非 - ajax示例)。例如,如果我们从ajax示例中获取getStuff,我们决定在将数据移交给ajax之前必须对数据进行一些处理,我们就会更改它:Fiddle

    getMoreStuff

    请注意我们如何使用它并没有改变:

    function getStuff() {
        // Create our own Deferred
        var d = new $.Deferred();
        display("getStuff starting ajax")
        $.ajax({
            url: "/echo/json/",
            type: "POST",
            data: {json: '{"message": "data from first request"}', delay: 1},
            dataType: "json",
            success: function(data) {
                // Modify the data
                data.message = "MODIFIED " + data.message;
    
                // Resolve with the modified data
                d.resolve(data);
            }
        });
        return d;
    }
    

    所有更改都在getStuff().then(getMoreStuff).then(doSomethingWithStuff); 内。

    这是关于整个“承诺”概念的一个伟大的事情(它完全不是jQuery特有的,但是jQuery为我们提供了方便的版本),它非常适合解耦。

答案 1 :(得分:5)

尝试

function getStuff() {
    return $.ajax({
        ...
        success: function(results) {
            // other functions involving results
        }
    });
}

function getMoreStuff(results) {
    return $.ajax({
        ...
        success: function(moreResults) {
            // other functions involving moreResults
        }
    );
}

然后

getStufff().done(function(){
    getMoreStuff().done(doSomethingWithStuff)
})

答案 2 :(得分:4)

传递接受参数的回调:

function getStuff( callback ) {
    $.ajax({
        ...
        success: function(results) {
            // callback with result
            callback(results);
        }
    });
}

function getMoreStuff(results, callback) {
    $.ajax({
        ...
        success: function(moreResults) {
            // callback with result
            callback(moreResults);
        }
    );
}

function doSomethingWithStuff(results, callback) {
    // process results via some means not described herein :)
    if (callback){
        // callback yet again with results, but only if callback provided this time
        callback(stillMoreResults);
    }
}

然后使用类似的东西:

getStuff(function(results) { 
    getMoreStuff(results, function(moreresults){
             doSomethingWithStuff(moreresults);
        });
    };

此模式通常对任何异步操作都有用。它不是特定于Ajax调用(我用它在JQuery中创建一个完整的动画棋盘游戏)。

答案 3 :(得分:2)

解决方案非常简单。您必须使用Publish–subscribe模式。 使用jQuery进行最简单的实现:

$('body').trigger('joined-game', [game_id, response]);

第一个参数是您要发布的事件名称,第二个参数是数据数组。

最佳做法是在最具体的DOM元素上触发事件,但如果您在多个页面上订阅相同的事件,并且不确定所有页面上是否存在DOM元素,则可以在{{1}上触发它或者一些“转储/合成”不可见的DOM元素总是存在于所有页面上。

body

然后您订阅您想要使用的活动。请记住,除了您的数据,第一个参数始终是事件。

此解决方案的另一个优点是您可以将代码拆分为多个文件。

更多详情:http://webility.pl/en/blog/entry/chaining-javascript-functions-without-dependecy-hell