避免Ajax回调地狱“嵌套”

时间:2017-02-15 17:52:33

标签: javascript jquery ajax promise

尝试构建一个链式选择表单,当我使用.on('change')使用$ .ajax请求获得所需数量的选项时,我已经完成了这个,但是当我开始失控时需要根据先前的选择显示可能存在或不存在的4个以下的下拉选项。有没有办法使用promises或其他东西重构此代码。也许指出我正确的方向?这是一个示例代码段。

$('#model').on('change',function(){


var modelID = $(this).val();
var yearsetID = $('#year').val();
var makesetID = $('#make').val();

if(modelID){
    $.ajax({
        url:'lib/ajaxData.php',
        type: "POST",
        data:{
            "action" : 'getsubmodels',
            "makeset_id" : makesetID,
            "yearset_id" : yearsetID,
            "model_id" : modelID
        },
        dataType: "html",
        success:function(html){
            if (html) {

               // found a submodel
               $('#submodel').html(html);

               $('#submodel').on('change',function(){
                            var submodelID = $(this).val();
                            var yearsetID = $('#year').val();
                            var makesetID = $('#make').val();
                            var modelsetID = $('#model').val();


                            $.ajax({
                                type:'POST',
                                url:'lib/ajaxData.php',
                                data: {
                                    "action" : 'getbodytypes',
                                    "year": yearsetID,
                                    "make" : makesetID,
                                    "model" : modelsetID,
                                    "submodel" : submodelID
                                },
                                success:function(html){


                                    // found a bodytype 
                                    if(html) {
                                            $('#bodytype').html(html);

                                            $('#bodytype').on('change',function(){
                                                //more dropdowns
                                            }


                                    }
                                }
                            });
                        });
                }else{

                            //no submodel

                        }
                    }
                }); 
            }else{
                $('.product').html('Select Some Values'); 
            }
        });

已编辑以显示HTML

以下是HTML的外观,除非该选项可用,否则会隐藏一些下拉列表。

<div class="select-boxes row">
        <div class="small-12 medium-1 columns">
        <select name="year" id="year">
            <option value="">Year </option>

        </select>

        </div>
        <div class="small-12 medium-1 columns">
        <select name="make" id="make">
            <option value="">Make</option>
        </select>

        </div>
        <div class="small-12 medium-1 columns end">
        <select name="model" id="model">
            <option value="">Model</option>
        </select>
        </div>
        <div class="small-12 medium-1 columns end submodel modifier" style="display:none;">
        <select name="submodel" id="submodel">
            <option value="">Submodel</option>
        </select>
        </div>
        <div class="small-12 medium-1 columns end bodytype modifier" style="display:none;">
        <select name="bodytype" id="bodytype">
            <option value="">BodyType</option>
        </select>
        </div>
        <div class="small-12 medium-1 columns end enginetype modifier" style="display:none;">
        <select name="engine" id="engine">
            <option value="">EngineType</option>
        </select>
        </div>
        <div class="small-12 medium-1 columns end drivetype modifier" style="display:none;">
        <select name="drive" id="drive">
            <option value="">DriveType</option>
        </select>
        </div>
    </div>

4 个答案:

答案 0 :(得分:1)

更具可读性

$('#model').on('change', modelOnChange);

    function modelOnChange() {
      var modelID = $(this).val(),
          yearsetID = $('#year').val(),
          makesetID = $('#make').val();

      (modelID) ? doStuff(modelID, yearsetID, makesetID) : doStuffIfNoId();
    }

    function doStuff(modelID, yearsetID, makesetID) {
      getSubModels(afterGetSubModels);
    }

    function doStuffIfNoId() {
      $('.product').html('Select Some Values');
    }

    function getSubModels(callback) {
      $.ajax({
        url     : 'lib/ajaxData.php',
        type    : "POST",
        data    : {
          "action"    : 'getsubmodels',
          "makeset_id": makesetID,
          "yearset_id": yearsetID,
          "model_id"  : modelID
        },
        dataType: "html",
        success : function (html) {
          if (callback)callback(html);
        }
      });
    }

    function afterGetSubModels(html) {
      if (html) doStuffModels();
    }

    function doStuffModels() {
      // found a submodel
      $('#submodel').html(html);

      $('#submodel').on('change', subModuleChange);
    }

    function subModuleChange() {
      var submodelID = $(this).val(),
          yearsetID = $('#year').val(),
          makesetID = $('#make').val(),
          modelsetID = $('#model').val();

      getBodyTypes(afterGetBodyTypes);

      function getBodyTypes(callback) {
        $.ajax({
          type   : 'POST',
          url    : 'lib/ajaxData.php',
          data   : {
            "action"  : 'getbodytypes',
            "year"    : yearsetID,
            "make"    : makesetID,
            "model"   : modelsetID,
            "submodel": submodelID
          },
          success: function (html) {
            if (callback)callback(html);
          }
        });
      }

      function afterGetBodyTypes(html) {
        // found a bodytype
        if (html) {
          $('#bodytype').html(html);

          $('#bodytype').on('change', function () {
            //more dropdowns
          });

        }
      }
    }

答案 1 :(得分:0)

首选方法是使用延迟事件而不是回调语法。在jQuery 1.5中引入时,他们使用的语法与ES6的承诺非常相似。这是SitePoint的主题tutorial

简而言之,您不是传递回调函数,而是将.done().fail().always().then()方法链接到{{1调用。 $.when调用是$.ajax()方法的参数。

以下是一个例子:

$.when()

希望这有点帮助!

答案 2 :(得分:0)

因为#submodel已经存在于所有这一切的开头并且没有被替换,所以你可以简单地将该甚至处理程序拉出嵌套。它不需要在里面。我还建议您将ajax处理切换为使用promises,因为它可以防止将来在复合操作上嵌套。

#bodytype似乎已经存在并且没有被替换,所以你可以为它做同样的事情。只需将事件处理程序从嵌套中拉出来。

如果动态替换或加载了这些元素中的任何一个,则可以使用委派事件处理。我将在下面显示两个版本的代码。

这是一个版本,假设#submodel#bodytype是常量而不是替换(如果更换子html则没问题):

$('#model').on('change',function(){
    var modelID = $(this).val();
    var yearsetID = $('#year').val();
    var makesetID = $('#make').val();

    if (modelID) {
        $.ajax({
            url:'lib/ajaxData.php',
            type: "POST",
            data: {
                "action" : 'getsubmodels',
                "makeset_id" : makesetID,
                "yearset_id" : yearsetID,
                "model_id" : modelID
            },
            dataType: "html",
        }).then(function(html){
            if (html) {
                // found a submodel
                $('#submodel').html(html);

            } else {
                //no submodel
            }
        });
    } else {
        $('.product').html('Select Some Values'); 
    }
});


$('#submodel').on('change',function(){
    var submodelID = $(this).val();
    var yearsetID = $('#year').val();
    var makesetID = $('#make').val();
    var modelsetID = $('#model').val();

    $.ajax({
        type:'POST',
        url:'lib/ajaxData.php',
        data: {
            "action" : 'getbodytypes',
            "year": yearsetID,
            "make" : makesetID,
            "model" : modelsetID,
            "submodel" : submodelID
        }
    }).then(function(html){
        // found a bodytype 
        if(html) {
            $('#bodytype').html(html);
        }
    });
});

$('#bodytype').on('change',function(){
    //more dropdowns
});

并且,如果动态替换了这些元素中的另一个,那么这是一个使用委托事件处理的版本,可以使用它。委托事件处理使用不同形式的jQuery .on()。而不是:

$(selector).on(event, fn);

它使用:

$(staticParentSelector).on(event, dynamicElementSelector, fn);

从技术上讲,这是如何工作的,它将事件处理程序分配给静态父对象(一个不动态替换的对象)。然后,它使用事件冒泡(其中在子级上发生的事件在父级层次结构中冒泡并提供给父元素)。当事件命中父元素时,jQuery委托事件处理检查该事件是否源于我们感兴趣的子选择器。如果是,则触发事件处理程序。这适用于动态创建的元素,因为没有事件处理程序实际附加到您感兴趣的动态创建的元素。相反,当它们冒泡到父级并且从那里触发事件处理程序时,会捕获该动态元素上发生的事件。

我们不会一直使用委托事件处理,因为它有一些应该注意的缺点。它的CPU效率要低一些,因为父母可能不得不检查来自很多不同孩子的大量冒泡事件,只是为了找到它正在寻找的那个。并且,您不能在其生命周期的早期处理该事件,因此如果您试图阻止默认操作(例如阻止提交表单),则事件冒泡时可能为时已晚给父母。而且,并非所有事件都不会冒泡(尽管大多数都是这样)。这些问题似乎都不适合你,所以这里是使用委托事件处理的代码。

由于我不知道你的HTML,我只选择document.body作为附加事件处理程序的静态元素。通常最好选择在实际HTML中没有被替换的最接近的父元素,这可能是一个更接近的父元素。

$(document.body).on('change', '#model', function(){
    var modelID = $(this).val();
    var yearsetID = $('#year').val();
    var makesetID = $('#make').val();

    if (modelID) {
        $.ajax({
            url:'lib/ajaxData.php',
            type: "POST",
            data: {
                "action" : 'getsubmodels',
                "makeset_id" : makesetID,
                "yearset_id" : yearsetID,
                "model_id" : modelID
            },
            dataType: "html",
        }).then(function(html){
            if (html) {
                // found a submodel
                $('#submodel').html(html);

            } else {
                //no submodel
            }
        });
    } else {
        $('.product').html('Select Some Values'); 
    }
});


$(document.body).on('change', '#submodel', function(){
    var submodelID = $(this).val();
    var yearsetID = $('#year').val();
    var makesetID = $('#make').val();
    var modelsetID = $('#model').val();

    $.ajax({
        type:'POST',
        url:'lib/ajaxData.php',
        data: {
            "action" : 'getbodytypes',
            "year": yearsetID,
            "make" : makesetID,
            "model" : modelsetID,
            "submodel" : submodelID
        }
    }).then(function(html){
        // found a bodytype 
        if(html) {
            $('#bodytype').html(html);
        }
    });
});

$(document.body).on('change', '#bodytype', function(){
    //more dropdowns
});

在问题的另一部分中,如果您只想在选择非默认值时显示下一个下拉列表,您可以这样做:

$(".select-boxes select").on("change", function(e) {
    // if we have a value here, then reveal the next select div
    if ($(this).val()) {
       // get parent div, then get next div, then show it
       $(this).closest(".small-12").next().show();
    }
});

答案 3 :(得分:0)

我发现你有很多嵌套的ajax电话。

  

这就是我们所说的Callback hell

我建议您使用Ajax的Promise方式代替回调,但这也会导致Promise hell

因此,唯一的解决方案是使用ES2017 async/await语法;所以,像这样改变你的代码。

$('#model').on('change',async function(){
    var modelID = $(this).val();
    var yearsetID = $('#year').val();
    var makesetID = $('#make').val();

    if (!modelID)
        return $('.product').html('Select Some Values');

    try {
        var html = await $.ajax({
            url:'lib/ajaxData.php',
            type: "POST",
            data:{
                "action" : 'getsubmodels',
                "makeset_id" : makesetID,
                "yearset_id" : yearsetID,
                "model_id" : modelID
            },
            dataType: "html"
        }) //If the ajax fail with 404, 500, or anything error, will be catched below...

        if (!html)
            return console.log('No submodel');

        $('#submodel').html(html);
        $('#submodel').on('change', async function(){
            var submodelID = $(this).val();
            var yearsetID = $('#year').val();
            var makesetID = $('#make').val();
            var modelsetID = $('#model').val();

            try {
                var html2 = await $.ajax({
                    type:'POST',
                    url:'lib/ajaxData.php',
                    data: {
                        "action" : 'getbodytypes',
                        "year": yearsetID,
                        "make" : makesetID,
                        "model" : modelsetID,
                        "submodel" : submodelID
                    }
                })

                if (!html2)
                    return console.log('No submodel 2')

                $('#bodytype').html(html);
                $('#bodytype').on('change',async function(){
                    //more dropdowns
                })
            } catch (e) {
                console.log('Error 2nd ajax', e)
            }
        })
    } catch (e) {
        console.log('Error in 1st ajax', e)
    }   
})