Комплексная работа с BaconJS (FRP)

Я пытаюсь выполнить эту относительно сложную операцию в BaconJs.

По сути, идея состоит в том, чтобы продолжать пробовать каждый check до тех пор, пока вы не получите статус «пройдено» или пока все они не потерпят неудачу. Подвох в том, что у статусов «ожидание» есть список Observables (созданный из запросов jquery ajax), которые разрешат проверку. Из соображений производительности вам нужно попробовать каждый Observable по порядку, пока либо все они не пройдут, либо один не выйдет из строя.

Вот полный псевдоалгоритм:

  • Go thru each check. A check contains an id and status = fail/pass/pending. If pending, it contains a list of observables.
    • If status = pass, then return the id (you're done!)
    • если статус = сбой, то попробуйте следующую проверку
    • if status = pending
      • try each observable in order
        • if observable result is 'false', then try the next check
      • если достигнут конец наблюдаемого списка и результат «истина», верните идентификатор (все готово!)

Вот код Бэкона. Это не работает, когда Observables являются запросами Ajax. По сути, происходит то, что он пропускает ожидающие проверки .... он не ждет возврата вызовов ajax. Если я помещу log() прямо перед filter(), он не будет регистрировать ожидающие запросы:

    Bacon.fromArray(checks)
      .flatMap(function(check) {

        return check.status === 'pass' ? check.id :
          check.status === 'fail' ? null :
            Bacon.fromArray(check.observables)
              .flatMap(function(obs) { return obs; })
              .takeWhile(function(obsResult) { return obsResult; })
              .last()
              .map(function(obsResult) { return obsResult ? check.id : null; });
      })
      .filter(function(contextId) { return contextId !== null; })
      .first();

ОБНОВЛЕНИЕ: код работает, когда проверки выглядят так: [сбой, сбой, ожидание]. Но это не работает, когда проверки выглядят так: [fail, pending, pass]


person U Avalos    schedule 26.06.2015    source источник
comment
То есть проверки должны оцениваться параллельно друг другу?   -  person Bergi    schedule 26.06.2015
comment
Нет, проверки идут последовательно. Вы не можете попробовать следующую проверку, пока не очистите текущую   -  person U Avalos    schedule 26.06.2015
comment
Похоже, на самом деле вы ищете обещания. Хотя свойства iirc Bacon fire-once и flatMap имеют одинаковую семантику.   -  person Bergi    schedule 26.06.2015
comment
Для такого типа проблем рабочий пример с jsfiddle или jsbin был бы действительно полезен.   -  person OlliM    schedule 26.06.2015


Ответы (3)


Я согласен с ответом @paulpdaniels на основе Rx. Проблема, по-видимому, заключается в том, что при использовании flatMap Bacon.js не будет ждать завершения вашего первого «контрольного потока», прежде чем запускать новый. Просто замените flatMap на flatMapConcat.

person raimohanska    schedule 26.06.2015
comment
Это помогло. Публикация полного ответа ниже для будущих потомков - person U Avalos; 26.06.2015

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

Он проходит [fail, pending, pass] в быстрой последовательности, fail возвращает ноль и отфильтровывается. pending запускает наблюдаемое, а затем получает pass, который немедленно возвращает check.id (Bacon может быть другим, но в RxJS flatMap не будет принимать возврат одного значения). Check.id проходит через filter и достигает first, после чего завершается и просто отменяет подписку на запрос ajax.

Быстрым решением, вероятно, будет использование concatMap, а не flatMap.

Однако в RxJS я бы реорганизовал это так, чтобы оно было (Отказ от ответственности непроверено):

Rx.Observable.fromArray(checks)
  //Process each check in order
  .concatMap(function(check) {
     var sources = {
       //If we pass then we are done
       'pass' : Rx.Observable.just({id : check.id, done : true}),
       //If we fail keep trying
       'fail' : Rx.Observable.just({done : false}),

       'pending' : Rx.Observable.defer(function(){ return check.observables;})
                                .concatAll()
                                .every()
                                .map(function(x) { 
                                  return x ? {done : true, id : check.id} : 
                                             {done : false};
                                })
     };

     return Rx.Observable.case(function() { return check.status; }, sources);
  })
  //Take the first value that is done
  .first(function(x) { return x.done; })
  .pluck('id');

Что делает вышеизложенное:

  1. Объединить все проверки
  2. Используйте оператор case для распространения вместо вложенных троичных элементов.
  3. Неудачно или пройдено быстро
  4. Если ожидание создать сглаженную наблюдаемую из check.observables, если все они истинны, то мы закончили, в противном случае переходим к следующему.
  5. Используйте значение предиката first, чтобы получить первое возвращенное значение, которое готово
  6. [Необязательно] удалить значение, которое нам нужно.
person paulpdaniels    schedule 26.06.2015
comment
к сожалению, хотя это может сработать, мне нужно выполнять каждую проверку в последовательном порядке, поэтому concatAll — это не то, что мне нужно (в Бэконе это combAsArray) - person U Avalos; 26.06.2015
comment
Не уверен, что понимаю. concatAll работает в последовательном режиме. Он не подписывается на следующие Observables, пока предыдущий не завершится. - person paulpdaniels; 26.06.2015
comment
к сожалению, если я не ошибаюсь, у Bacon, похоже, нет эквивалента #concatAll. (могу ошибаться, но не вижу). Спасибо хоть. Ваш ответ подтолкнул меня в правильном направлении - person U Avalos; 26.06.2015

Спасибо @raimohanska и @paulpdaniels. Ответ заключается в использовании #flatMapConcat. Это превращает то, что в основном представляет собой список асинхронных вызовов, выполняемых параллельно, в последовательность вызовов, выполняемых по порядку (и обратите внимание, что последняя «проверка» запрограммирована так, чтобы она всегда проходила, так что она всегда что-то выводит):

   Bacon.fromArray(checks)
      .flatMapConcat(function(check) {

        var result = check();

        switch(result.status) {
          case 'pass' :
          case 'fail' :
            return result;
          case 'pending' :
            return Bacon.fromArray(result.observables)
              .flatMapConcat(function(obs) { return obs; })
              .takeWhile(function(obsResult) { return obsResult.result; })
              .last()
              .map(function (obsResult) { return obsResult ? {id: result.id, status: 'pass'} : {status: 'fail'}; });

        }
      })
      .filter(function(result) { return result.status === 'pass'; })
      .first()
      .map('.id');
person U Avalos    schedule 26.06.2015