javascript - 如何通过减少请求量来改进我的 AJAX 实时搜索

标签 javascript jquery ajax events filtering

我正在构建一个 AJAX 实时搜索页面。到目前为止,一切都按预期工作,但我注意到我正在进行大量的 AJAX 调用。

我知道发生这种情况的地点和原因,但我找不到阻止这些 AJAX 调用发生的方法。

我会尽量给出一个快速的解释,然后粘贴下面的代码。

在页面的左侧,我有一堆过滤器(例如位置、半径、兴趣、参与度、日期范围等)。他们每个人都有一个事件监听器(例如更改)。当其中一个监听器被触发时,我更新查询参数并通过 AJAX 请求结果。

现在问题出现了,例如有人选择了 10 个兴趣(共有 36 个),然后他分享了 URL,它看起来像这样:

http://localhost/codeigniter/nl-be/sociale-teambuildings/zoeken?locations=&distance=0&minemployees=0&maxemployees=1000&minprice=0&maxprice=50000&interests=1%3B2%3B3%3B4%3B5%3B6%3B7%3B8%3B9%3B10&sdgs=&startdate=2018-12-03&enddate=2019-12-03&engagements=

现在首先,我会说我正在为我的复选框使用 iCheck.js。这意味着检查“ifChecked”事件。 由于列表包含 10 个兴趣条目,“ifChecked”事件将被触发 10 次,从而导致 10 个 AJAX 请求。现在将其与 5 个 SDG、2 个参与、3 个位置……结合考虑,我最终得到大量 AJAX 请求。 同时删除我所有的兴趣(有一个“删除”链接)时,它会触发“ifUnchecked”事件 10 次,从而再次执行 10 个 AJAX 请求。

这是我的完整 JS 代码,我已经尝试使用 HTML 和所有内容创建一个 jsfiddle,但是该代码有点与 CodeIgniter 框架混合在一起,很难放在其中。但是 JS 代码足以让图片清晰可见。

//Set vars available to entire scope for filtering
var searchLocation = null;
var searchRadius = 0;
var searchMin = 0;
var searchMax = 1000;
var searchMinPrice = 0;
var searchMaxPrice = 50000;
var searchSelectedInterests = [];
var searchSelectedSdgs = [];
var searchStartDate = moment().format('YYYY-MM-DD');
var searchEndDate = moment().add(1, 'years').format('YYYY-MM-DD');
var searchSelectedEngagements = [];

var getUrl = window.location;
var baseUrl = getUrl .protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];

$(document).ready(function(){
    'use strict';

    // Square Checkbox & Radio
    $('.skin-square input').iCheck({
        checkboxClass: 'icheckbox_square-blue'
    });
    $('.searchinterests input').on('ifChecked', function(event) {
        var interestid = event.target.value;
        searchSelectedInterests.push(interestid);
        updateURLParameters();
        performSearch();
    });
    $('.searchinterests input').on('ifUnchecked', function(event) {
        var interestid = event.target.value;
        var arrayPos = $.inArray(interestid, searchSelectedInterests);
        if (arrayPos > -1) {
            searchSelectedInterests.splice(arrayPos, 1);
        }
        performSearch();
    });
    $('.searchsdgs input').on('ifChecked', function(event) {
        var sdgid = event.target.value;
        searchSelectedSdgs.push(sdgid);
        updateURLParameters();
        performSearch();
    });
    $('.searchsdgs input').on('ifUnchecked', function(event) {
        var sdgid = event.target.value;
        var arrayPos = $.inArray(sdgid, searchSelectedSdgs);
        if (arrayPos > -1) {
            searchSelectedSdgs.splice(arrayPos, 1);
        }
        performSearch();
    });
    $('.searchengagements input').on('ifChecked', function(event) {
        var social_engagement_id = event.target.value;
        searchSelectedEngagements.push(social_engagement_id);
        updateURLParameters();
        performSearch();
    });
    $('.searchengagements input').on('ifUnchecked', function(event) {
        var social_engagement_id = event.target.value;
        var arrayPos = $.inArray(social_engagement_id, searchSelectedEngagements);
        if (arrayPos > -1) {
            searchSelectedEngagements.splice(arrayPos, 1);
        }
        performSearch();
    });

    var searchLocationSelect = $('.stb-search-location');
    var radiusSlider = document.getElementById('radius-slider');
    var minMaxSlider = document.getElementById('min-max-slider');
    var priceSlider = document.getElementById('price-slider');
    var daterangepicker = $('#searchdaterange');
    var curr_lang = $('#curr_lang').val();

    switch(curr_lang) {
        case 'nl':
            moment.locale('nl');
            daterangepicker.daterangepicker({
                minDate: moment(),
                startDate:  moment(),
                endDate: moment().add(1, 'years'),
                ranges: {
                    'Volgende week': [moment(), moment().add(1, 'weeks')],
                    'Volgende maand': [moment(), moment().add(1, 'months')],
                    'Volgende 3 maanden': [moment(), moment().add(3, 'months')],
                    'Volgende 6 maanden': [moment(), moment().add(6, 'months')],
                    'Volgend jaar': [moment(), moment().add(1, 'years')]
                },
                alwaysShowCalendars: true,
                locale: {
                    "customRangeLabel": "Vrije keuze",
                    "format": "DD-MM-YYYY"
                }
            });
            break;
        case 'en':
            moment.locale('en');
            daterangepicker.daterangepicker({
                minDate: moment(),
                startDate:  moment(),
                endDate: moment().add(1, 'years'),
                ranges: {
                    'Next week': [moment(), moment().add(1, 'weeks')],
                    'Next month': [moment(), moment().add(1, 'months')],
                    'Next 3 months': [moment(), moment().add(3, 'months')],
                    'Next 6 months': [moment(), moment().add(6, 'months')],
                    'Next year': [moment(), moment().add(1, 'years')]
                },
                alwaysShowCalendars: true,
                locale: {
                    "customRangeLabel": "Free choice",
                    "format": "DD-MM-YYYY"
                }
            });
            break;
        case 'fr':
            moment.locale('fr');
            daterangepicker.daterangepicker({
                minDate: moment(),
                startDate:  moment(),
                endDate: moment().add(1, 'years'),
                ranges: {
                    'Semaine prochaine': [moment(), moment().add(1, 'weeks')],
                    'Mois prochain': [moment(), moment().add(1, 'months')],
                    '3 mois prochains': [moment(), moment().add(3, 'months')],
                    '6 mois prochains': [moment(), moment().add(6, 'months')],
                    'L\'année prochaine': [moment(), moment().add(1, 'years')]
                },
                alwaysShowCalendars: true,
                locale: {
                    "customRangeLabel": "Libre choix",
                    "format": "DD-MM-YYYY"
                }
            });
            break;
    }
    daterangepicker.on('hide.daterangepicker', function (ev, picker) {
        var startdate = picker.startDate.format('YYYY-MM-DD');
        var enddate = picker.endDate.format('YYYY-MM-DD');
        setStartDate(startdate);
        setEndDate(enddate);
        updateURLParameters();
        performSearch();
    });

    if (searchLocationSelect.length) {
        searchLocationSelect.selectize({
            create: false,
            sortField: {
                field: 'text',
                direction: 'asc'
            },
            dropdownParent: 'body',
            plugins: ['remove_button'],
            onChange: function(value) {
                setLocation(value);
                var size = value.length;
                if (size == 1) {
                    enableRadius(radiusSlider);
                } else {
                    disableAndResetRadius(radiusSlider);
                }
                updateURLParameters();
                performSearch();
            }
        });
    }

    noUiSlider.create(radiusSlider, {
        start: [0],
        step: 5,
        range: {
            'min': 0,
            'max': 100
        }
    });
    var radiusNodes = [
        document.getElementById('radius-value')
    ];
    // Display the slider value and how far the handle moved
    // from the left edge of the slider.
    radiusSlider.noUiSlider.on('update', function (values, handle, unencoded, isTap, positions) {
        var value = values[handle];
        radiusNodes[handle].innerHTML = Math.round(value);
    });
    radiusSlider.noUiSlider.on('set', function (value) {
        setRadius(value);
        updateURLParameters();
        performSearch();
    });

    var minmax_slider_options = {
        start: [0,1000],
        behaviour: 'drag',
        connect: true,
        tooltips: [wNumb({
            decimals: 0
        }), wNumb({
            decimals: 0
        })],
        range: {
            'min': 0,
            'max': 1000
        },
        step: 5
    };
    noUiSlider.create(minMaxSlider, minmax_slider_options);
    var minMaxNodes = [
        document.getElementById('minmax-lower-value'),
        document.getElementById('minmax-upper-value')
    ];
    // Display the slider value and how far the handle moved
    // from the left edge of the slider.
    minMaxSlider.noUiSlider.on('update', function (values, handle, unencoded, isTap, positions) {
        var value = values[handle];
        minMaxNodes[handle].innerHTML = Math.round(value);
    });
    minMaxSlider.noUiSlider.on('set', function (values) {
        setMin(values[0]);
        setMax(values[1]);
        updateURLParameters();
        performSearch();
    });

    var price_slider_options = {
        start: [0,50000],
        behaviour: 'drag',
        connect: true,
        tooltips: [wNumb({
            decimals: 0
        }), wNumb({
            decimals: 0
        })],
        range: {
            'min': 0,
            'max': 50000
        },
        step: 250
    };
    noUiSlider.create(priceSlider, price_slider_options);
    var priceNodes = [
        document.getElementById('price-lower-value'), // 1000
        document.getElementById('price-upper-value')  // 3500
    ];
    // Display the slider value and how far the handle moved
    // from the left edge of the slider.
    priceSlider.noUiSlider.on('update', function (values, handle, unencoded, isTap, positions) {
        var value = values[handle];
        priceNodes[handle].innerHTML = '€'+Math.round(value);
    });
    priceSlider.noUiSlider.on('set', function (values) {
        setMinPrice(values[0]);
        setMaxPrice(values[1]);
        updateURLParameters();
        performSearch();
    });

    /** Reset methods **/
    $('#resetLocations').on('click', function(e) {
        e.preventDefault();
        var locationInputField = $('.stb-search-location');
        var control = locationInputField[0].selectize;
        control.clear();
    });

    $('#resetRadius').on('click', function(e) {
        e.preventDefault();
        document.getElementById('radius-slider').noUiSlider.set(0);
    });

    $('#resetMinMax').on('click', function(e) {
        e.preventDefault();
        document.getElementById('min-max-slider').noUiSlider.set([0,1000]);
    });

    $('#resetPrice').on('click', function(e) {
        e.preventDefault();
        document.getElementById('price-slider').noUiSlider.set([0,50000]);
    });

    $('#resetInterests').on('click', function(e) {
        e.preventDefault();
        searchSelectedInterests = [];
        $("input[name='interests[]']").iCheck('uncheck');
    });

    $('#resetSdgs').on('click', function(e) {
        e.preventDefault();
        searchSelectedSdgs = [];
        $("input[name='sdgs[]']").iCheck('uncheck');
    });

    $('#resetDate').on('click', function(e) {
        e.preventDefault();
        $('#searchdaterange').data('daterangepicker').setStartDate(moment());
        $('#searchdaterange').data('daterangepicker').setEndDate(moment().add(1, 'years'));

        var startdate = $('#searchdaterange').data('daterangepicker').startDate.format('YYYY-MM-DD');
        var enddate = $('#searchdaterange').data('daterangepicker').endDate.format('YYYY-MM-DD');
        setStartDate(startdate);
        setEndDate(enddate);
        performSearch();
    });

    $('#resetEngagement').on('click', function(e) {
        e.preventDefault();
        searchSelectedEngagements = [];
        $("input[name='engagement[]']").iCheck('uncheck');
    });

    // Set initial parameters (and pre-fill the filters based on query params) 
    setupConfig(radiusSlider);
});


function getQueryStringValue(){
    var currentURL = new URI();

    var queryParams = currentURL.query(true);
    if ($.isEmptyObject(queryParams) === false) {
        return queryParams;
    } else {
        return undefined;
    }
}

//In here we read the query parameters from the URL and set the filters to the query parameters (+ do initial filtering)
function setupConfig(radiusSlider) {
    var queryParams = getQueryStringValue();

    if (queryParams !== undefined) {
        var locations = queryParams.locations;
        if (locations !== undefined) {
            var locationsArray = locations.split(";");
            fillLocations(locationsArray);
            if (locationsArray.length != 1) {
                disableAndResetRadius(radiusSlider);
            }
        } else {
            disableAndResetRadius(radiusSlider);
        }

        var distance = queryParams.distance;
        if (distance !== undefined) {
            if (locationsArray.length != 1) {
                disableAndResetRadius(radiusSlider);
            } else {
                document.getElementById('radius-slider').noUiSlider.set(distance);
            }
        }

        var minEmployees = queryParams.minemployees;
        var maxEmployees = queryParams.maxemployees;
        if ((minEmployees !== undefined) && (maxEmployees !== undefined)) {
            document.getElementById('min-max-slider').noUiSlider.set([minEmployees,maxEmployees]);
        }

        var minPrice = queryParams.minprice;
        var maxPrice = queryParams.maxprice;
        if ((minPrice !== undefined) && (maxPrice !== undefined)) {
            document.getElementById('price-slider').noUiSlider.set([minPrice,maxPrice]);
        }

        var interests = queryParams.interests;
        if (interests !== undefined) {
            var interestsArray = interests.split(";");
            fillInterests(interestsArray);
        }

        var sdgs = queryParams.sdgs;
        if (sdgs !== undefined) {
            var sdgsArray = sdgs.split(";");
            fillSdgs(sdgsArray);
        }

        var startdate = queryParams.startdate;
        var enddate = queryParams.enddate;
        if ((startdate !== undefined) && (enddate !== undefined)) {
            $('#searchdaterange').data('daterangepicker').setStartDate(moment(startdate));
            $('#searchdaterange').data('daterangepicker').setEndDate(moment(enddate));

            var startdate = $('#searchdaterange').data('daterangepicker').startDate.format('YYYY-MM-DD');
            var enddate = $('#searchdaterange').data('daterangepicker').endDate.format('YYYY-MM-DD');
            setStartDate(startdate);
            setEndDate(enddate);
        }

        var engagements = queryParams.engagements;
        if (engagements !== undefined) {
            var engagementsArray = engagements.split(";");
            fillEngagements(engagementsArray);
        }
    } else {
        disableAndResetRadius(radiusSlider);
        performSearch();
    }
}

function fillLocations(locations) {
    var selectize = $('.stb-search-location');
    selectize[0].selectize.setValue(locations);
}

function fillInterests(interests) {
    for (var i = 0; i < interests.length; i++) {
        var checkboxId = "interest-"+interests[i];
        var checkbox = $('#'+checkboxId);
        checkbox.iCheck('check');
    }
}

function fillSdgs(sdgs) {
    for (var i = 0; i < sdgs.length; i++) {
        var checkboxId = "sdg-"+sdgs[i];
        var checkbox = $('#'+checkboxId);
        checkbox.iCheck('check');
    }
}

function fillEngagements(engagements) {
    for (var i = 0; i < engagements.length; i++) {
        var checkboxId = "eng-"+engagements[i];
        var checkbox = $('#'+checkboxId);
        checkbox.iCheck('check');
    }
}

function getCurrLang() {
    return $('#curr_lang').val();
}

function getCurrLangSegment() {
    return $('#curr_abbr').val();
}

function getLocation() {
    return $('#location').val();
}

function setLocation(value) {
    searchLocation = value;
}

function setRadius(value) {
    searchRadius = value;
}

function disableAndResetRadius(radiusSlider) {
    radiusSlider.noUiSlider.set(0);
    radiusSlider.setAttribute('disabled', true);
}

function enableRadius(radiusSlider) {
    radiusSlider.removeAttribute('disabled');
}

function setMin(value) {
    searchMin = value;
}

function setMax(value) {
    searchMax = value;
}

function setMinPrice(value) {
    searchMinPrice = value;
}

function setMaxPrice(value) {
    searchMaxPrice = value;
}

function setStartDate(value) {
    searchStartDate = value;
}

function setEndDate(value) {
    searchEndDate = value;
}

function performSearch() {
    $('#stb-items-placeholder').html('<div id="loading"><span>'+res.StbSearchPlaceholder+'</span></div>');

    var searchOptions = {
        type: 'POST',
        url: baseUrl + '/dashboard/socialteambuildings/search/getFilteredStbs',
        dataType: 'json'
    };

    var filterdata = {
        "curr_lang" : getCurrLang(),
        "curr_abbr" : getCurrLangSegment(),
        "location" : getLocation(),
        "interests": searchSelectedInterests,
        "sdgs": searchSelectedSdgs
    };

    var options = $.extend({}, searchOptions, {
        data: filterdata
    });

    // ajax done & fail
    $.ajax(options).done(function (data) {
        var mustacheJsonData = data.result;

        var html = Mustache.render( $('#stbItemGen').html(), mustacheJsonData );
        $('#stb-items-placeholder').html( html );
    });
}

function updateURLParameters() {
    if (searchLocation != null) {
        var locationsUrlString = searchLocation.join(";");
    }

    var distanceUrlString = Math.round(searchRadius[0]);
    var minEmployeesUrlString = Math.round(searchMin);
    var maxEmployeesUrlString = Math.round(searchMax);
    var minPriceUrlString = Math.round(searchMinPrice);
    var maxPriceUrlString = Math.round(searchMaxPrice);
    var interestUrlString = searchSelectedInterests.join(";");
    var sdgUrlString = searchSelectedSdgs.join(";");
    var startDateUrlString = searchStartDate;
    var endDateUrlString = searchEndDate;
    var engagementUrlString = searchSelectedEngagements.join(";");

    var params = {locations: locationsUrlString, distance: distanceUrlString, minemployees: minEmployeesUrlString, maxemployees: maxEmployeesUrlString, minprice: minPriceUrlString, maxprice: maxPriceUrlString, interests: interestUrlString, sdgs: sdgUrlString, startdate: startDateUrlString, enddate: endDateUrlString, engagements: engagementUrlString};
    var query = $.param(params);
    addURLParameter(query);
}

//This function removes all the query parameters and adds the completely newly built query param string again
function addURLParameter(queryString){
    var currentUrl = window.location.href;
    var urlNoQueryParams = currentUrl.split("?");
    var baseUrl = urlNoQueryParams[0];
    var result = baseUrl + "?" + queryString;
    window.history.replaceState('', '', result);
}

例如,我尝试在删除选项上使用 e.stopPropagation() 和 e.stopImmediatePropagation()。这不会阻止事件返回到 iCheck 库。

最佳答案

Debounce 在这里不起作用,因为问题不在于一个事件监听器在短时间内有太多请求,而是许多独立的事件监听器一个接一个地触发。

可能的解决方案:

  1. 添加一个按钮,例如 SEARCH,它会实际执行搜索,而不是由个别更新触发。这是解决许多独立监听器问题的好而简单的解决方案。

  2. 如果您不想添加新按钮,请执行以下操作。使用 setInterval 添加时间间隔以使用 AJAX 执行搜索。并有一个标志是否应该执行搜索。然后,当复选框上的任一监听器更改时,只需将标志设置为 true。此外,如果请求已经在进行中,则在当前请求完成之前不要发出另一个 AJAX 请求。

伪代码如下:

var do_search = false, timer = null, doing_ajax = false, TROTTLE = 500;
timer = setTimeout(performSearch, TROTTLE);

function performSearch()
{
    if ( !do_search || doing_ajax )
    {
        timer = setTimeout(performSearch, TROTTLE);
        return;
    }
     doing_ajax = true;
     do_search = false;
     // do the ajax request here
     // ...
     // NOTE: on ajax complete reset the timer, eg timer = setTimeout(performSearch, TROTTLE);
     // and set doing_ajax = false;
}

// then your checkboxes listeners will simply update the do-search flag eg:
    $('.searchsdgs input').on('ifChecked', function(event) {
        var sdgid = event.target.value;
        searchSelectedSdgs.push(sdgid);
        updateURLParameters();
        //performSearch();
        do_search = true;
    });
    $('.searchsdgs input').on('ifUnchecked', function(event) {
        var sdgid = event.target.value;
        var arrayPos = $.inArray(sdgid, searchSelectedSdgs);
        if (arrayPos > -1) {
            searchSelectedSdgs.splice(arrayPos, 1);
        }
        //performSearch();
        do_search = true;
    });
    $('.searchengagements input').on('ifChecked', function(event) {
        var social_engagement_id = event.target.value;
        searchSelectedEngagements.push(social_engagement_id);
        updateURLParameters();
        //performSearch();
        do_search = true;
    });
    $('.searchengagements input').on('ifUnchecked', function(event) {
        var social_engagement_id = event.target.value;
        var arrayPos = $.inArray(social_engagement_id, searchSelectedEngagements);
        if (arrayPos > -1) {
            searchSelectedEngagements.splice(arrayPos, 1);
        }
        //performSearch();
        do_search = true;
    });

注意 您可以调整TROTTLE 间隔以在更直接的交互性和更少的 AJAX 请求之间取得平衡。尝试不同的值以了解您的应用的感受。

注意 2 您可以在此示例的基础上构建一个多反跳函数,例如通过清除超时并在每个单独的监听器中重置它(而不是简单地设置 do_search=true 你可以设置 do_search=true 并清除之前的间隔并重新设置)。这将确保只执行最后一次更新。

关于javascript - 如何通过减少请求量来改进我的 AJAX 实时搜索,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53592232/

相关文章:

javascript - 如何在 Vue 自定义指令中添加事件监听器?

javascript - 为什么我的圆圈以一定 Angular 从直线表面弹起?

javascript - 缓慢滚动到顶部在 javascript 中不起作用

java - 创建随 secret 码会生成长度为 0 : Blame JavaScript or Java Servlet? 的密码

javascript - 为什么 Jquery mobile 在桌面浏览器上显示移动友好的外观?

javascript - 更新操作后 Rails 中的 View 背景

javascript - 使用 AJAX 发送到 MVC 中的 Controller 时,嵌套的 javascript 对象属性为 null

php - 当一个输入被填满时,如何使用ajax获取mysql数据

javascript - 为什么这个clearInterval总是在函数完成时起作用?

asp.net - 正在寻找一个好的 jQuery 数据网格插件