Ограничение скорости нокаута с обновлениями AJAX

Я пытаюсь создать форму HTML, которая обновляет некоторые из своих значений в зависимости от того, что выбрано в раскрывающемся списке. Модель представления выглядит примерно так:

function RoomViewModel() {
    var self = this;

    self.companyOptions = @Html.Raw({ ... });
    self.companyValue = ko.observable().extend({ rateLimit: 5000 });
    self.companyInfo = ko.observable();
    ko.computed(function() {
        if (self.companyValue()) {
            $.getJSON('@Html.GenerateActionLinkUrl("GetCompanyAndPlans")', {}, self.companyInfo);
        }
    });
}

ko.options.deferUpdates = true;
ko.applyBindings(new RoomViewModel());

Затем я привязываю свой раскрывающийся список select к companyValue, и если я несколько раз меняю выбор, только через 5 секунд computed срабатывает и отображает текущее выбранное значение. Это близко к тому, что я хочу, но одна проблема заключается в том, что при первом изменении раскрывающегося списка вам не нужно ждать 5 секунд - он должен немедленно выполнить вызов JSON. Ограничение скорости заключается в том, чтобы остановить дальнейшие запросы JSON между первым изменением и 5 секундами позже. Итак, как я могу заставить его немедленно выполнить запрос JSON и обновить для первого изменения?


person Jez    schedule 15.07.2018    source источник
comment
Вот отличная статья об ограничении наблюдаемой скорости Knockout: knockoutjs.com/documentation/rateLimit-observable.html ~ Надеюсь, это поможет.   -  person Nerdi.org    schedule 15.07.2018


Ответы (2)


var ratelim = 0; // global rate limit 

function RoomViewModel() {
    var self = this;

    self.companyOptions = @Html.Raw({ ... });

    if(ratelim == 0){
      self.companyValue = ko.observable().extend({ rateLimit: ratelim }); // trigger the change without a delay 
      ratelim = 5000; // update the ratelim so the next request has a 5 second delay 
    } else { // ratelimit is not 0 (not first request), go ahead with regular rate-limited change: 
      self.companyValue = ko.observable().extend({ rateLimit: ratelim }); // extend the observable, with current ratelim
    }

    self.companyInfo = ko.observable();
    ko.computed(function() {
        if (self.companyValue()) {
            $.getJSON('@Html.GenerateActionLinkUrl("GetCompanyAndPlans")', {}, self.companyInfo);
        }
    });
}

ko.options.deferUpdates = true;
ko.applyBindings(new RoomViewModel());

Я считаю, что это должно сработать. Я использовал глобальную переменную (ratelim), чтобы ваша функция могла определить, является ли это первым запросом или нет... На самом деле вы должны изменить значение переменной с true/false для того, выполняется ли запрос или нет, чтобы было 0 ограничение скорости, если пользователь какое-то время «бездействует». Ака, если второй запрос происходит через 20 секунд после первого, нет необходимости в 5-секундной задержке.

person Nerdi.org    schedule 15.07.2018
comment
Хм??? Как это будет работать? Он запустится только один раз, во время создания новой модели RoomViewModel. - person Jez; 15.07.2018
comment
Что ж, если вы запускаете его только один раз, вам просто нужно обновить ограничение скорости после первого раза. {rateLimit: 0} должно быть по умолчанию, а затем вам нужно будет обновить его до 5000, как только это будет сделано один раз. - person Nerdi.org; 15.07.2018

Интересная проблема. Я начал играть с ним и пришел к выводу, что для этого вам нужен собственный расширитель. Я нашел один, который имитирует rateLimit, и с некоторыми изменениями он действительно решает проблему ваша потребность.

При этом вы должны быть в состоянии сделать:

self.companyValue = ko.observable().extend({ customRateLimit: 5000 });

И пусть начальное изменение будет мгновенным, а любое последующее будет ограничено по скорости.

Вот скрипка

Вот фрагмент исполняемого кода:

ko.extenders.customRateLimit = function(target, timeout) {
  var writeTimeoutInstance = null;
  var currentValue = target();
  var updateValueAgain = false;
  var interceptor;
  var isFirstTime = true

  if (ko.isComputed(target) && !ko.isWriteableObservable(target)) {
    interceptor = ko.observable().extend({
      customRateLimit: timeout
    });
    target.subscribe(interceptor);
    return interceptor;
  }

  return ko.dependentObservable({
    read: target,
    write: function(value) {
      var updateValue = function(value) {
        if (isFirstTime) {
          target(value);
          isFirstTime = false;
        } else {
          if (!writeTimeoutInstance) {
            writeTimeoutInstance = setTimeout(function() {
              writeTimeoutInstance = null;
              if (updateValueAgain) {
                updateValueAgain = false;
                updateValue(currentValue);
              }
              target(value);
            }, timeout);
          }
        }
      }
      currentValue = value;
      if (!writeTimeoutInstance)
        updateValue(currentValue);
      else
        updateValueAgain = true;
    }
  });
}

function AppViewModel() {
  this.text = ko.observable().extend({
    customRateLimit: 1000
  });
  this.rateLimited = ko.computed(this.text).extend({
    customRateLimit: 1000
  });
}

ko.applyBindings(new AppViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-debug.js"></script>

<h4>Change value is default so move the focus out of the input to change values.</h4>

<div>
  Enter Text: <input type='text' data-bind='value: text' />
</div>
<div>
  Rete Limited <small>(after the first change)</small>: <input type='text' data-bind='value: text' />
</div>
<div>
  Rete Limited Computed <small>(after the first change)</small>: <input type='text' data-bind='value: rateLimited' />
</div>

Обратите внимание, что после того, как вы введете текст в первое текстовое поле, изменения сразу же вступят в силу в других. Однако любое изменение после первого задерживается.

При этом вы можете расширить наблюдаемые и вычисляемые наблюдаемые.

person Akrion    schedule 22.08.2018