簡體   English   中英

避免“嵌套” Ajax回調地獄

[英]Avoiding Ajax Callback Hell “Nesting”

嘗試構建鏈式選擇表單時,當使用.on('change')使用$ .ajax請求進行選擇時,我已經完成了此操作,但是當我需要顯示時,事情開始失去控制根據先前的選擇可能存在或可能不存在的4個以上的下拉選項。 有沒有一種方法可以使用Promise或其他方式重構此代碼。 也許會指出正確的方向? 這是一個示例代碼片段。

$('#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>

更具可讀性

$('#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
          });

        }
      }
    }

因為#submodel在所有這些操作的開始就已經存在並且沒有被替換,所以您只需將那個處理程序從嵌套中拉出即可。 它不需要在里面。 我還建議您將ajax處理切換為使用Promise,因為這將使您將來避免在復合操作上嵌套。

看來#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().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();
    }
});

首選方法是使用延遲事件,而不是回調語法。 他們在jQuery 1.5中引入,它們使用的語法與ES6所承諾的非常相似。 這里有一個教程由SitePoint的話題。

簡而言之,不是傳遞回調函數,而是將.done() .fail() .always().always() .then()方法鏈接到$.when調用上。 $.ajax()調用是$.when()方法的參數。

這是一個例子:

$.when(
    // Make an AJAX request.
    $.ajax( .. )
    // The when block will return the jqXHR Object
    // You could also use the $.post() or $.get() methods here.
)
.done(
    // any code here will only run if the .when() request is successful
)
.fail(
    // Code here will only run if the .when() request fails
)
.always(
    // Code placed here will always run. Regardless of success or failure.
    // This is usually placed last for additional cleanup, etc. that is
    // performed whether the .when() returned success or failure.
)

希望這會有所幫助!

我看到您有很多嵌套的ajax調用。

這就是我們所說的Callback hell

我建議您將Promise方式與Ajax一起使用,而不要使用回調,但這也會導致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)
    }   
})

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM