如何确定一个jQuery AJAX调用何时完成?

3
我有一个国家下拉菜单,使用jquery ajax调用填充国家名称。问题在于我需要在确定选择哪个国家之前知道调用何时完成。也就是说,在带有ajax调用的函数之后调用的任何函数都太早了(在下拉列表填充之前),因此以下函数将会崩溃。 我不想使用async=false (因为这会引起更多问题,并且是一种不好的做法)。 那么,实现这一点的正确方法是什么?
这里有一个通用的ajax函数来检索数据并用参数id填充指定类型的select下拉菜单(ddl):
   function RetrieveDD(ddl, ddtype, id) {
        var servicename;
        switch (ddtype) {
            case 'country':
                servicename = "LoadCountries";
                break;
            case 'state':
                servicename = "LoadStateProvinces";
                break;
            case 'city':
                servicename = "LoadCityLocalities";
                break;
            default:
                servicename = "";
        }
        if (servicename != "") {
             $.ajax({
                url: 'DDService.asmx/' + servicename,
                dataType: 'json',
                method: 'post',
                data: { ID: id },
                success: function (data) {
                    ddl.empty();
                    $.each(data, function (index) {
                        ddl.append($("<option" + (index == 0 ? " selected" : "") + "></option>").val(this['V']).html(this['T']));
                    });
                },
                error: function (err) {
                    console.log('Error (RetrieveDD): ' + JSON.stringify(err, null, 2));
                }
            });
        };
    };

我有一个测试按钮,执行此操作并想要获取所选选项的值(这将调用填充状态,获取第一个选定状态的值,并再次调用以填充城市)。问题非常明显:在不知道前一个下拉列表的选择值的情况下,我无法说填充下一个下拉列表(因此我必须有一种方法来等待下拉列表首先填充)。
        $('#btnTest').click(function () {
           RetrieveDD($("#ddlCountries"), "country", 0);

            var cid = $("#ddlCountries").val();  //this is still undefined because ajax call above not completed 
            alert("cid = " + cid);    //normally another call would be made here to populate the states
        });

点击按钮后,您立即看到一个弹出的警告框(cid值为空),并且只有在您从警告对话框中单击[确定]按钮后才填充国家下拉列表。基本上这是错乱的顺序。
如果我将async:false添加到ajax例程中,则会显示具有正确cid值的警报,但是由于人们已经发布了不要使用async:false的帖子,因此我希望避免使用它,但不确定如何正确进行操作。 (如果有人想知道,我正在从旧的dotnet webforms网站中删除所有MSAjaxToolkit垃圾,并替换为更快,更小,更容易修复的jQuery ajax。)
以下是HTML片段:
    <input type="button" id="btnTest" value="Test" /><br />
    <div id="dd">
        <select id="ddlCountries"></select><br />
        <select id="ddlStates"></select><br />
        <select id="ddlCities"></select>
    </div>

**************回应为什么我认为这不是重复的********* 需要添加对较旧版本的IE支持 (Windows 7、8、8.1以及某些服务器版本)。IE不支持箭头函数或Promise(或2015年后的大多数ECMA方法),因此很多推荐的答案虽然非常好,但没有包含这些内容的代码。我会尝试找出解决方案并在此处发布。


您可以将 var cid = $("#ddlCountries").val(); 放到成功处理程序本身中,或者将一个回调传递到 RetrieveDD 中来运行它(或以类似的方式使用 promises)。 - Gavin
6个回答

2
JQuery.ajax() 实现了 Promise 接口,因此您可以使用 ajax 调用返回的 Promise 对象将其传回到调用函数。这可以使用 Promise.then() 来在 ajax 调用完成后调用代码,我认为这比使用回调函数更加简洁清晰。
   function RetrieveDD(ddl, ddtype, id) {
    var servicename;
    var promise;
    switch (ddtype) {
        case 'country':
            servicename = "LoadCountries";
            break;
        case 'state':
            servicename = "LoadStateProvinces";
            break;
        case 'city':
            servicename = "LoadCityLocalities";
            break;
        default:
            servicename = "";
    }
    if (servicename != "") {
         promise = $.ajax({
            url: 'DDService.asmx/' + servicename,
            dataType: 'json',
            method: 'post',
            data: { ID: id },
            success: function (data) {
                ddl.empty();
                $.each(data, function (index) {
                    ddl.append($("<option" + (index == 0 ? " selected" : "") + "></option>").val(this['V']).html(this['T']));
                    console.log('update list');
                });
            },
            error: function (err) {
                console.log('Error (RetrieveDD): ' + JSON.stringify(err, null, 2));
            }
        });
    };

    return promise;
};


$('#btnTest').click(function () {
    var promise = RetrieveDD($("#ddlCountries"), "country", 0);
    if (promise) {
        promise.then(function(value) {
            var cid = $("#ddlCountries").val();                  
            console.log('done');
        });
    }
});

2
与John M的答案类似,但用法不同。
返回$.ajax承诺并使用.done()
if (servicename!="") 
    return $.ajax({...
// else return null

$('#btnTest').click(function () {
    var ajax = RetrieveDD($("#ddlCountries"), "country", 0);
    if (ajax) {  
        ajax.done(function(data) {  
            console.log('done');
        });
    }
});

很遗憾,IE不支持这个功能。也没有胖箭头。(至少Win 8.1及更早版本没有)我希望IE能够消失。 - MC9000
这里没有“胖箭头”。你觉得哪一部分不支持?return吗? - freedomn-m

1
在你的函数中使用回调函数:
function RetrieveDD(ddl, ddtype, id, callback = false) {
    // ...
    if (servicename != "") {
         $.ajax({
            url: 'DDService.asmx/' + servicename,
            dataType: 'json',
            method: 'post',
            data: { ID: id },
            success: function (data) {
                // ...
                if(typeof callback === 'function'){
                    callback(data);
                }
            },
            error: function (err) {
                // ...
                if(typeof callback === 'function'){
                    callback(err);
                }
            }
        });
    };
};

RetrieveDD($("#ddlCountries"), "country", 0, (data_or_error)=>{
    var cid = $("#ddlCountries").val();
});

这正是我一直在寻找的!老实说,我不确定如何使用JavaScript来完成这个任务,但作为一个主要从事服务器端开发的人,这对我来说很有意义。 - MC9000
1
如果你想要真正时髦和现代化,你也可以使用 Promise。不过在这个阶段可能有点过头了。 - Gavin
只需要弄清楚如何将它从“fat arrow”转换为旧语法,因为一半的IE用户仍在使用Windows 8.1及以下版本(不支持版本6)。 - MC9000
在这个例子中,经典的 function(data_or_error){} 可以很好地替代 (data_or_error)=>{} - Gavin
我已经搞定了 - 请看我的答案。谢谢。 - MC9000

1
Ajax有自己的成功、错误和完成回调函数。
$.ajax({
type:"POST",
url:"loadData",
data:{id:id},
success:function(data){
console.log(data);
},
error:function(err){
console.log(err);
},
complete:function(){
console.log("Ajax call completed !!!!");
}
})



1
嗨,这是一个有用的答案,但OP已经在他们的代码中演示了使用它们。他们需要知道如何从调用函数中访问它们。 - freedomn-m

1

我认为你可以在Ajax调用中使用这个语法

$.ajax({
            type: 'post',
            url: 'your url',
            data: new FormData(this),
            contentType: false,
            data_type: 'json',
            cache: false,
            processData: false,
            beforesend: function () {
                //your code
            },
            success: function (data) {
                // your code
            }, complete: function () {
                // your code
            }
        });

OP已经在他们的代码片段中使用了这个语法。他们需要知道如何从调用函数中访问它们。 - freedomn-m

0

这是我想出来的可用方案(在转译器es6console.com的帮助下)。 我在注释区域留下了原始版本以供参考。 额外的代码片段是为了适当地切换其他字段(肯定需要更多的清理/简化)(“其他”是所有下拉菜单中的最后一个选项——尽管我不认为会很快出现新的国家,除非它在火星上。笑!)

$(document).ready(function () {


    $('#btnTest').click(function () {
        //RetrieveDD($("#ddlCountries"), "country", 0, (data_or_error) => {
        //    LoadStates();
        //});
        // IE compatible
        RetrieveDD($("#ddlCountries"), "country", 0, function (data_or_error) {
            LoadStates();
        });
    });

    function LoadStates() {
        Visible("#txtOtherCountry", 0);
        var cid = $('#ddlCountries').val();
        if (cid > -1) {
            if (cid == 0) {
                //other - turn on Other Country field
                Visible("#txtOtherCountry", 1);
                // set both state & city dropdowns to Other and make their Other fields display
                // clear state dropdown & add "Other" with a value of 0 to it & open
                SetOtherState();
                SetOtherCity();
            } else {
                //populate states for selected country
                //RetrieveDD($("#ddlStates"), "state", cid, (data_or_error) => {
                //    LoadCities();
                //});
                // IE compatible
                RetrieveDD($("#ddlStates"), "state", cid, function (data_or_error) {
                    LoadCities();
                });
            }
        } else {
            // make selection (future)
        }
    }

    function SetOtherState() {
        var ddlState = $("#ddlStates");
        ddlState.empty();
        ddlState.append($("<option selected></option>").val("0").html("Other"));
        Visible("#txtOtherState", 1);
    }

    function SetOtherCity() {
        var ddlCity = $("#ddlCities");
        ddlCity.empty();
        ddlCity.append($("<option selected></option>").val("0").html("Other"));
        Visible("#txtOtherCity", 1);
    }

    function Visible(elem, mode) {
        $(elem).val('');
        $(elem).css('visibility', (mode == 1 ? "visible" : "hidden"));
    }

    function LoadCities() {
        Visible("#txtOtherState", 0);
        Visible("#txtOtherCity", 0);
        var sid = $('#ddlStates').val();
        if (sid > -1) {
            if (sid == 0) {
                //other - turn on Other State field
                Visible("#txtOtherState", 1);
                // clear city dropdown & add "Other" with a value of 0 to it & open
                SetOtherCity();
            } else {
                RetrieveDD($("#ddlCities"), "city", sid);
            }
        } else {
            // make selection (future)
        }
    }


    $("#ddlCountries").change(function () {
        LoadStates();
    });

    $("#ddlStates").change(function () {
        LoadCities();
    });


    $("#ddlCities").change(function () {
        if ($("#ddlCities").val() == 0) {
            Visible("#txtOtherCity", 1);
        } else {
            Visible("#txtOtherCity", 0);
        };
    });


    //function RetrieveDD(ddl, ddtype, id, callback = false) {
    //    var servicename;
    //    switch (ddtype) {
    //        case 'country':
    //            servicename = "LoadCountries";
    //            break;
    //        case 'state':
    //            servicename = "LoadStateProvinces";
    //            break;
    //        case 'city':
    //            servicename = "LoadCityLocalities";
    //            break;
    //        default:
    //            servicename = "";
    //    }
    //    if (servicename != "") {
    //        var oldEvent = ddl.attr("onchange");
    //        ddl.attr("onchange", ""); //remove change event
    //        $.ajax({
    //            url: 'DDService.asmx/' + servicename,
    //            dataType: 'json',
    //            method: 'post',
    //            data: { ID: id },
    //            success: function (data) {
    //                ddl.empty();
    //                $.each(data, function (index) {
    //                    ddl.append($("<option" + (index == 0 ? " selected" : "") + "></option>").val(this['V']).html(this['T']));
    //                });
    //                if (typeof callback === 'function') {
    //                    callback(data);
    //                }
    //            },
    //            error: function (err) {
    //                console.log('Error (RetrieveDD): ' + JSON.stringify(err, null, 2));
    //                if (typeof callback === 'function') {
    //                    callback(err);
    //                }
    //            }
    //        });
    //        ddl.attr("onchange", oldEvent); //add change event back
    //    };
    //};

    //IE compatible
    function RetrieveDD(ddl, ddtype, id) {
        var callback = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];

        var servicename;
        switch (ddtype) {
            case 'country':
                servicename = "LoadCountries";
                break;
            case 'state':
                servicename = "LoadStateProvinces";
                break;
            case 'city':
                servicename = "LoadCityLocalities";
                break;
            default:
                servicename = "";
        }
        if (servicename != "") {
            var oldEvent = ddl.attr("onchange");
            ddl.attr("onchange", "");
            $.ajax({
                url: 'DDService.asmx/' + servicename,
                dataType: 'json',
                method: 'post',
                data: { ID: id },
                success: function success(data) {
                    ddl.empty();
                    $.each(data, function (index) {
                        ddl.append($("<option" + (index == 0 ? " selected" : "") + "></option>").val(this['V']).html(this['T']));
                    });
                    if (typeof callback === 'function') {
                        callback(data);
                    }
                },
                error: function error(err) {
                    console.log('Error (RetrieveDD): ' + JSON.stringify(err, null, 2));
                    if (typeof callback === 'function') {
                        callback(err);
                    }
                }
            });
            ddl.attr("onchange", oldEvent);
        };
    };
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接