【问题标题】:Action method execution sequence动作方法执行顺序
【发布时间】:2017-08-16 19:16:36
【问题描述】:

我在这里陷入了一个非常奇怪的境地。解释起来很复杂,但我会尽力而为。

我的 UI 顶部有 4 个导航 <a> 按钮 - 中间总是有一个表单 - 在底部我有上一个和下一个按钮。

在 MVC 中使用 Ajax.BeginForm 构造表单

对于顶部的每个导航链接 <a> 元素,我有一个 JavaScript 函数

var LoadTabs = function (e, arg) {  
    // This is to validate a form if one of the top links is clicked and form has incomplete fields...
    if (arg !== "prev" && arg !== "next") {
        if (!window.ValidateForm(false)) return false;
    }

    var url = $(this).attr('data'); // this contains link to a GET action method
    if (typeof url != "undefined") {
        $.ajax(url, { context: { param: arg } }).done(function (data) {                
            $('#partialViewContainer').html(data);
        });
    }
}

上面的这个函数绑定到页面加载时的每个顶部链接。

$('.navLinks').on('click', LoadTabs);

我的下一个和上一个按钮基本上会触发点击事件,即LoadTabs 函数。

$('button').on('click', function () { 
        if (this.id === "btnMoveToNextTab") {
            if (!window.ValidateForm(true)) return false;                

            $.ajax({
                url: url,
                context: { param: 'next' },
                method: "GET",
                data: data,
                success: function(response) {
                    if (typeof response == 'object') {
                        if (response.moveAhead) {
                            MoveNext();
                        }
                    } else {
                        $('#mainView').html(response);
                    }
                    ScrollUp(0);
                }
            });
        } 

        if (this.id === "btnMoveToPreviousTab") {
            MoveBack();
        }
        return false;
    });


MoveNext() Implementation is as below:

function MoveNext() {        
    var listItem = $('#progressbarInd > .active').next('li');        

    listItem.find('.navLink').trigger('click', ['next']);
    ScrollUp(0);
}

问题是,由于某些原因,当导航链接 3 处于活动状态并且我点击下一步按钮时 - 而不是首先通过 form.submit() 发布表单 - 导航 4 被触发 - 因此导航 4 的 GET 在之前运行形成导航 3 的 POST。

我的 ValidateForm 方法基本上只是检查表单是否存在并且是否有效然后提交,否则返回 false。如下:

function ValidateForm(submit) {

    var form = $('form');

    // if form doesn't exist on the page - return true and continue
    if (typeof form[0] === "undefined") return true;

    // now check for any validation errors
    if (submit) {
        if (!$(form).valid()) {                
            return false;
        } else {
            $(form).submit();
        }
    } 
    else {
        return true;
    }

    return true;
}

我的猜测是 form.submit 确实会被触发,但由于提交需要更长的时间才能完成,它会继续按钮 onclick 事件中的下一个代码块。

我首先认为这是 C# 问题,因为在 POST 中我通过几个循环保存了大量数据,任何处理繁重的代码块我都参与其中

var saveTask =  Task.Factory.StartNew(() => ControllerHelper.SomeMethod(db, model));
Task.WaitAll(saveTask);

WaitAll 将等待并暂停执行,直到 SomeMethod 完成执行。我不确定如何在 javascript 中锁定进程并等待它完成执行。因为我认为如果我能以某种方式将 form.submit() 锁定在 ValidateForm 中,直到其完成处理......也许通过回调方法......

如果有人能给我指明正确的方向,我将不胜感激。如果您需要更多信息,请告诉我,我很乐意提供!

【问题讨论】:

    标签: javascript c# jquery asp.net-mvc model-view-controller


    【解决方案1】:

    编辑关于返回承诺反模式

    正如 jfriend00 评论原始答案返回承诺是反模式的方式,更好的方法是:

    function ValidateForm(){
        // do your logic
        if (pass) {
            return $.post("urlToPost");
        else {
            return $.Deferred().reject(xxx).promise();
        }
    }
    

    更多信息,结帐https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

    原答案

    我会试着想象一下你的问题:

    你想要的是首先运行ValidateForm,当它通过时(这样当表单发布完成时),然后运行MoveNext

    您的问题是,MoveNextValidateForm 完成之前被调用。

    如果我是正确的,那么原因是因为 Javascript 本质上是异步编程,因此在某些事情完成时使用回调来触发。

    所以在你的情况下,你需要使用 promise 来实现它。

    基本上你需要做的是:

    1. ValiateForm返回承诺
    2. 只有在ValidateForm 完成后才执行MoveNext

    所以代码应该是这样的:

    function ValiateForm(){
        var dfd = jQuery.Deferred();
    
        // do your logic
        if(pass){
            $.post("urlToPost"
        } else {
            // also failed
            dfd.reject();
        }
    
        // return the promise
        return dfd.promise();
    }
    

    那么你的下一步按钮就像

    $('button').on('click', function () { 
            if (this.id === "btnMoveToNextTab") {
    
                $.when(window.ValidateForm(true)).then(function(){
                // after success post form
                $.ajax({
                            url: url,
                            context: { param: 'next' },
                            method: "GET",
                            data: data,
                            success: function(response) {
                                if (typeof response == 'object') {
                                    if (response.moveAhead) {
                                        MoveNext();
                                    }
                                } else {
                                    $('#mainView').html(response);
                                }
                                ScrollUp(0);
                            }
                        });
                if (this.id === "btnMoveToPreviousTab") {
                    MoveBack();
                }
            } 
            return false;
            });                
    
    
    });
    

    有关承诺结帐的更多信息

    https://api.jquery.com/deferred.promise/

    完成后运行

    https://api.jquery.com/deferred.then/

    【讨论】:

    • 非常感谢@Alan - 我会试一试告诉你!
    • 再次感谢@Alan。我终于弄清楚了一切,在延迟实现方面遇到了一个小问题,但它也得到了照顾。 *.com/questions/43008781/… 谢谢。快乐编码:)
    • 这里的第一个代码块是promise anti-pattern。因为$.post() 已经返回了一个promise,所以没有必要在它周围包装另一个promise/deferred。您可以只返回 $.post() 已经返回的承诺,而无需创建自己的承诺。
    • @jfriend00 感谢您提供的信息。只是想进一步澄清最佳做法。因为 OP 提到它将验证表单并发布 - 所以我的理解是它可能会在发布之前无法通过客户端验证 - 因此拒绝 if(pass) 的承诺失败。所以不可能只从$.post() 返回承诺,因为它可能没有发生。在这种情况下它仍然是反模式吗?或者有什么更好的方法来实现这一目标?再次感谢您指出这一点,只是想在编辑我的答案以供将来参考之前确保它是正确的
    • 我发布了一种在这种情况下避免反模式的方法作为另一个答案。
    【解决方案2】:

    我发布这是给 Alan 的便条,因为它是多行代码,我无法在评论中使其可读。当您有时运行异步操作而有时不运行时,为了避免 Promise 反模式,可以使用这个:

    function ValidateForm(){
        // do your logic
        if (pass) {
            return $.post("urlToPost");
        else {
            return $.Deferred().reject(xxx).promise();
        }
    }
    

    这样,您总是会返回一个承诺。在一种情况下,承诺来自$.ajax()。在另一种情况下,您只是返回了一个被拒绝的承诺,因为操作已经失败。

    【讨论】:

    • 感谢您的信息,我还将编辑我的答案以包含此信息。谢谢阿甘
    • 这很有趣。我可以 return form.submit(callback...); 代替 return $.post(url); 吗?
    • @Johny - form.submit() 似乎没有返回承诺。太糟糕了。我实际上不确定如何从中获得 jQuery 承诺。可能有一种方法,但似乎没有记录。可能是form.submit().promise(),但你得试一试才能看到。
    • @jfriend00 不,看起来提交没有返回承诺。我最终使用了 return $.post("urlToPost", form.serialize());我再也无法重现这种行为了。感谢你们俩抽出时间并提供帮助。我对 Jquery 的延迟对象有了更好的理解。
    最近更新 更多