【问题标题】:Jquery When and Deferred object, broken function flowJquery When 和 Deferred 对象,中断的函数流
【发布时间】:2015-06-19 05:00:45
【问题描述】:

我正在使用 $.when 和 .done 来确保在保存数据后关闭窗口。但是,这似乎没有按预期工作。

工作流程是,用户点击“保存并关闭”按钮,首先保存数据,触发打印并关闭窗口。但是保存数据和关闭窗口同时发生,导致打印失败。

我已经阅读了 when..then 和 deferred object。尝试在下面的代码中实现它,有时它可以工作,但大多数时候它会中断。

$("#btnSaveAndClose").click(function (event) {
    $.when(zSaveSomeData()).done(function (value) {
        zCloseMyWindow();
    });
});

function zSaveSomeData() {
    return zSaveMasterData(masterdata, function () {  
        return zSaveDetailData();
    });
};

function zSaveMasterData(masterdata, fnAfterSave) {
    return $.ajax({
        type: 'POST',
        contentType: 'application/json',
        url: '/api/masterdata/',
        data: JSON.stringify(masterdata),
        success: function (data) {
            fnAfterSave();
        }
    });
};

function zSaveDetailData() {
    var selectedDataGroups;
    // some logic here

    zSaveDetails(selectedDataGroups);

};

function zSaveDetails(selectedDataGroups) {
    var deferred = $.Deferred();
    $.ajax({
        type: 'POST',
        contentType: 'application/json',
        url: '/api/detaildata/',
        data: JSON.stringify(selectedDataGroups),
        success: function (data) {
            var printableGroupIDs = [];
            $.each(data, function () {
                if (this.IsPrintable)
                    printableGroupIDs.push(this.ID);
            });

            if (printableGroupIDs.length > 0) {
                zPrintGroups(printableGroupIDs);
            }
            deferred.resolve('done');
        }
    });

    zAuditSave();
    return deferred.promise();
};

function zPrintGroups(newGroupIDs) {
    // calls external program to print groups

};

function zCloseWindow() {
    window.close();
};

function zAuditSave() {
    $.ajax({
        type: 'POST',
        contentType: 'application/json',
        url: '/api/audit'
        success: function (data) {

        }
    });
};

唯一的问题是保存调用内部的其他方法来获取相同的主数据和详细数据。也有几个 ajax 调用。一个不寻常的事情是,在数据保存后,有一个调用 VB 代码实际上触发了一个 Print。我很困惑为什么要在执行其他方法之前关闭窗口。任何帮助将不胜感激。

【问题讨论】:

  • return zSaveDetailData(); zCleanUp(); - 等等什么?
  • 什么是zAuditSave()
  • @Bergi,感谢您指出这一点,实际上该代码是我编辑时遗留下来的。它不在那里。我添加了返回测试,但确实清理了代码。
  • zAuditSave() 是另一种调用 webApi 来审核保存的方法。我也会添加那个方法。

标签: javascript jquery jquery-deferred .when


【解决方案1】:

这里的问题是您的代码不依赖于fnAfterSave() 的完成时间。

简短回答:不要混合使用 success 方法、callbackspromises - 使用一种模式并坚持使用 - 最简单的模式是 promises

$("#btnSaveAndClose").click(function (event) {
    zSaveSomeData().then(function() { zCloseMyWindow(); });
});

function zSaveSomeData() {
    return zSaveMasterData(masterdata).then(function(data) { zSaveDetailData() });
};

function zSaveMasterData(masterdata) {
    return $.ajax({
        type: 'POST',
        contentType: 'application/json',
        url: '/api/masterdata/',
        data: JSON.stringify(masterdata)
    });

    //remove success callback here as it breaks the chaining
};

【讨论】:

    【解决方案2】:

    您的问题似乎是您在 ajax success 回调中执行异步操作。 $.ajax 返回的承诺仍然会在收到响应后立即解析 - 并在异步 zSaveDetailData() 完成之前执行您的 done 回调。

    因此,要链接异步操作,始终使用then。甚至用于同步动作,它使顺序清晰。

    在使用 Promise 时不要使用 success 回调。你也don't need deferreds。你可能还想看看these generic rules,尤其是你永远不要忘记return你想要等待的异步函数的promise。

    $("#btnSaveAndClose").click(function (event) {
        zSaveSomeData().then(zCloseMyWindow);
    });
    
    function zSaveSomeData() {
        return zSaveMasterData(masterdata).then(zSaveDetailData);
    }
    
    function zSaveMasterData(masterdata) {
        return $.ajax({
            type: 'POST',
            contentType: 'application/json',
            url: '/api/masterdata/',
            data: JSON.stringify(masterdata),
        });
    }
    
    function zSaveDetailData() {
        var selectedDataGroups;
        // some logic here
    
        return zSaveDetails(selectedDataGroups);
    //  ^^^^^^
    }
    
    function zSaveOrderGroups(selectedDataGroups) {
        return $.ajax({
    //  ^^^^^^
            type: 'POST',
            contentType: 'application/json',
            url: '/api/detaildata/',
            data: JSON.stringify(selectedDataGroups)
        }).then(function(data) {
    //    ^^^^^^^^^^^^^^^^^^^^^^
            var printableGroupIDs = [];
            $.each(data, function () {
                if (this.IsPrintable)
                     printableGroupIDs.push(this.ID);
            });
            if (printableGroupIDs.length > 0) {
                return zPrintGroups(printableGroupIDs);
    //          ^^^^^^
            }
        }).then(zAuditSave);
    //    ^^^^^^^^^^^^^^^^^
    }
    
    function zPrintGroups(newGroupIDs) {
        // calls external program to print groups
    }
    
    function zCloseWindow() {
        window.close();
    }
    
    function zAuditSave() {
        return $.ajax({
    //  ^^^^^^
            type: 'POST',
            contentType: 'application/json',
            url: '/api/audit'
        });
    }
    

    【讨论】:

    • 我尝试重构代码以仅使用一种模式,即promises。仍然在 zPrintGroups() 之前调用 zCloseWindow()。我尝试调试,发现控件命中return zSaveDetails(selectedDataGroups);这行,然后当我进入时,执行zSaveDetails()中的ajax以保存数据,然后控件直接进入zCloseWindow()并关闭窗口因此,zSaveDetails() 中的 zPrintGroups() 方法永远不会被触发。
    • 也许你得到的是.then(zCloseWindow()) 而不是.then(zCloseWindow)
    • 我核对了一下,是zSaveSomeData().then(zCloseWindow);。我还检查了所有其他方法调用,它们看起来不错。
    • 你能在某个地方做一个演示吗?我很有信心这应该会奏效。
    • 当然,我会尝试创建一些可演示的东西。虽然不知道如何进行ajax调用。但是,让我试试。
    【解决方案3】:

    对我来说,代码被过度划分为函数,其中一些只是为其他函数做前端。

    我更愿意将点击处理程序视为一个综合的主例程,它对三个返回承诺的函数zSaveMasterData()zSaveDetails()zAuditSave() 进行排序,然后关闭窗口。因此,当前的一些功能将被点击处理程序所包含。

    $("#btnSaveAndClose").click(function(event) {
        zSaveMasterData(masterdata).then(function() {
            var selectedDataGroups;
            /* some logic here */
            var detailsSaved = zSaveDetails(selectedDataGroups).then(function(data) {
                var printableGroupIDs = $.map(data, function (obj) {
                    return obj.IsPrintable ? obj.ID : null;
                });
                if (printableGroupIDs.length > 0) {
                    // calls external program to print groups
                }
            });
            // Here, it is assumed that zSaveDetails() and zAuditSave() can be performed in parallel.
            // If the calls need to be sequential, then the code will be slightly different.            
            return $.when(detailsSaved, zAuditSave());
        }).then(function() {
            window.close();
        });
    });
    
    function zSaveMasterData(masterdata) {
        return $.ajax({
            type: 'POST',
            url: '/api/masterdata/',
            contentType: 'application/json',
            data: JSON.stringify(masterdata),
        });
    };
    
    function zSaveDetails(selectedDataGroups) {
        return $.ajax({
            type: 'POST',
            contentType: 'application/json',
            url: '/api/detaildata/',
            data: JSON.stringify(selectedDataGroups)
        });
    };
    
    function zAuditSave() {
        return $.ajax({
            type: 'POST',
            contentType: 'application/json',
            url: '/api/audit'
        });
    };
    

    请注意使用 ajax 调用的三个函数中的返回值。这些回报对于排序过程至关重要。

    一个潜在的更大的问题,没有在问题中(也没有在这个答案中解决)是如何从错误中恢复。据推测,如果保存顺序中途失败,则数据库将不一致。最好放弃这种客户端排序方法,转而支持客户端视为单个操作的服务器端事务。

    【讨论】:

    • 我不认为你对$.map 的使用确实有效
    • @Bergi,我的代码都没有经过测试,我很容易犯错。从内存来看,jQuery.map()Array.prototype.map() 的不同之处在于 jQuery 版本 (a) 在返回 null 时过滤掉一个元素,并且 (b) 返回一个 jQuery 包装的数组(因此需要 .get())。跨度>
    • 是的,它应该被命名为concatMap,因为它处理null 和数组值。但实际上 $.map(arr, fn) 确实返回数组,而 $(arr).map(fn) 返回 jquery 包装器。
    • 啊,好吧,我应该首先查一下。文档还说“在函数内,this 指的是全局(窗口)对象”。哎呀,编辑了。谢谢你的收获。
    猜你喜欢
    • 2023-03-08
    • 2011-07-27
    • 1970-01-01
    • 2012-03-11
    • 2012-11-12
    • 2014-05-25
    • 2016-01-08
    • 2012-10-16
    • 2017-03-02
    相关资源
    最近更新 更多