Подтвердить что ты не робот

Rxjs - Отменить будущие возвращаемые наблюдаемые

Итак, вот мой наблюдаемый код:

  var suggestions =
        Rx.Observable.fromEvent(textInput, 'keyup')
          .pluck('target','value')
          .filter( (text) => {
              text = text.trim();
              if (!text.length) // empty input field
              {
                  this.username_validation_display("empty");
              }
              else if (!/^\w{1,20}$/.test(text))
              {
                  this.username_validation_display("invalid");
                  return false;
              }
              return text.length > 0;
          })
          .debounceTime(300)
          .distinctUntilChanged()
          .switchMap(term => {
              return $.ajax({
                type: "post",
                url: "src/php/search.php",
                data: {
                  username: term,
                  type: "username"
                }
              }).promise();
            }
          );
  suggestions.subscribe(
    (r) =>
    {
        let j = JSON.parse(r);
        if (j.length)
        {
            this.username_validation_display("taken");
        }
        else
        {
            this.username_validation_display("valid");
        }
    },
    function (e)
    {
        console.log(e);
    }
  );

Проблема заключается в том, когда вход пуст. У меня есть еще один фрагмент кода, который в основном возвращает "error: empty input", но он становится переопределяемым возвращаемым наблюдаемым. Поэтому мне было интересно, есть ли способ игнорировать все наблюдаемые, если text.length равно 0, но также переписывать, когда длина текста не равна нулю.

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

4b9b3361

Ответ 1

Может, это сработает? Идея состоит в том, чтобы включить пустой пул в коммутационную карту, чтобы текущий наблюдаемый был выключен, то есть отброшен и в этом случае.

  var suggestions =
        Rx.Observable.fromEvent(textInput, 'keyup')
          .pluck('target','value')
          .filter( (text) => {
              text = text.trim();
              if (!text.length) // empty input field
              {
                  this.username_validation_display("empty");
              }
              else if (!/^\w{1,20}$/.test(text))
              {
                  this.username_validation_display("invalid");
                  return false;
              }
          })
          .debounceTime(300)
          .distinctUntilChanged()
          .switchMap(text=> {
              if (text.length > 0) {
              return $.ajax({
                type: "post",
                url: "src/php/search.php",
                data: {
                  username: text,
                  type: "username"
                }
              }).promise()}
              return Rx.Observable.empty();
            }
          );

Ответ 2

Я бы предложил переместить вызов debounceTime сразу после потока ввода текста и выполнить фильтрацию уже в отключенном потоке. Таким образом, действительное, но устаревшее значение не будет проникать в вызов ajax. Кроме того, нам может потребоваться "отменить" вызов ajax (или, точнее, игнорировать его ответ), когда произойдет новый, возможно, недействительный ввод, поскольку мы не хотим, чтобы "недопустимый" статус был переопределен с результатом продолжающегося асинхронного вызов. takeUntil может помочь с этим. Также обратите внимание, что Rx.Observable.ajax() можно использовать вместо $.ajax().

Кстати, может быть удобно иметь выделенный поток "статусов проверки", чтобы this.username_validation_display(validationStatus) выполнялся в одном месте внутри подписки.

Я предполагаю, что это что-то вроде этого:

const term$ = Rx.Observable.fromEvent(textInput, "keyup")
    .pluck("target", "value")
    .map(text => text.trim())
    .distinctUntilChanged()
    .share();

const suggestion$ = term$
    .debounceTime(300)
    .filter(term => getValidationError(term).length === 0)
    .switchMap(term => Rx.Observable
        .ajax({
            method: "POST",
            url: "src/php/search.php",
            body: {
                username: term,
                type: "username"
            }
        })
        .takeUntil(term$)
    )
    .map(result => JSON.parse(result));

const validationStatus$ = Rx.Observable.merge(
    term$.map(getValidationError),
    suggestion$.map(getSuggestionStatus));

// "empty", "invalid", "taken", "valid" or ""
validationStatus$
    .subscribe(validationStatus => this.username_validation_display(validationStatus));

// Helper functions
function getValidationError(term) {
    if (term.length === 0) {
        return "empty";
    }

    if (!/^\w{1,20}$/.test(term)) {
        return "invalid";
    }

    return "";
}

function getSuggestionStatus(suggestion) {
    return suggestion.length > 0 ? "taken" : "valid";
}

Поведение вышеприведенного кода также будет различным в том смысле, что между точкой времени, когда пользователь набрал действительный ввод и точка, когда предложение поступило с сервера, username_validation_display будет иметь пустое значение, т.е. помимо "пустых", "недействительных", "принятых" и "действительных" статусов, также будет промежуточный пустой статус, который может быть заменен некоторым индикатором выполнения в пользовательском интерфейсе.

Обновление: Я также могу подумать об альтернативном подходе, который должен иметь эквивалентное поведение, но соответствующий код может выглядеть несколько более простым:

const username$ = Rx.Observable.fromEvent(textInput, "keyup")
    .pluck("target", "value")
    .map(text => text.trim())
    .distinctUntilChanged();

const validationStatus$ = username$.switchMap(username => {
    const validationError = getValidationError(username);
    return validationError ?
        Rx.Observable.of(validationError) :
        Rx.Observable.timer(300)
            .switchMapTo(getAvailabilityStatus$(username))
            .startWith("pending");
});

// "invalid", "empty", "taken", "valid" or "pending"
validationStatus$.subscribe(
    validationStatus => this.username_validation_display(validationStatus));

// Helper functions
function getAvailabilityStatus$(username) {
    return Rx.Observable
        .ajax({
            method: "POST",
            url: "src/php/search.php",
            body: {
                username: username,
                type: "username"
            }
        })
        .map(result => JSON.parse(result))
        .map(suggestions => suggestions.length > 0 ? "taken" : "valid");
}

function getValidationError(username) {
    if (username.length === 0) {
        return "empty";
    }

    if (!/^\w{1,20}$/.test(username)) {
        return "invalid";
    }

    return null;
}

Таким образом мы проверяем каждый отдельный вход синхронно. Если ввод пуст или недействителен (определяется вызовом getValidationError()), мы немедленно испускаем ошибку проверки. В противном случае мы немедленно выдаем "ожидающий" статус и запускаем таймер, который будет выдавать асинхронную проверку доступности имени пользователя через 300 мс. Если новый текст прибывает до истечения времени таймера или наличия статуса доступности с сервера, мы отменяем внутренний поток и снова запускаем проверку, благодаря switchMap. Поэтому он должен работать почти так же, как с debounceTime.

Ответ 3

Вы можете использовать свои исходные потоки значений в качестве уведомителя для своего вызова ajax, чтобы прервать его значение. Это должно дать вам достаточно контроля для добавления дополнительной обработки для отображения сообщений, если поле пустое, не перезаписывая предыдущие результаты ajax.

const inputValues = Rx.Observable.fromEvent(document.getElementById("inputField"), "keyup")
  .pluck("target","value")
  .distinctUntilChanged()
  .do(val => console.log('new value: ' + val));


inputValues
  .filter(val => val.length > 0)
  .switchMap(text => doAjaxCall(text)
    .takeUntil(inputValues)
  )
  .subscribe(val => console.log('received a value: ' + val));


function doAjaxCall(inputValue) {
  return Rx.Observable.of(inputValue)
    .do(val => console.log('ajax call starting for: ' + val))
    .delay(1000)
    .do(val => console.log('ajax call returned for: ' + val));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.2/Rx.js"></script>
<label>input field<input type="text" id="inputField" /></label>