Как избежать отправки в середине отправки

Внутри приложения React architected React я извлекаю данные из хранилища и хотел бы создать действие, чтобы запросить эту информацию, если она не существует. Однако я столкнулся с ошибкой, когда диспетчер уже отправляет сообщения.

Мой желаемый код выглядит примерно так:

getAll: function(options) {
  options = options || {};
  var key = JSON.stringify(options);
  var ratings = _data.ratings[key];

  if (!ratings) {
    RatingActions.fetchAll(options);
  }

  return ratings || [];
}

Однако прерывисто терпит неудачу, когда диспетчер уже отправляет действие, с сообщением Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.. Я часто делаю запросы в ответ на изменение состояния приложения (например, диапазон дат). Мой компонент, где я делаю запрос, в ответ на событие изменения из AppStore имеет следующее:

getStateFromStores: function() {
  var dateOptions = {
    startDate: AppStore.getStartISOString(),
    endDate: AppStore.getEndISOString()
  };

  return {
    ratings: RatingStore.getAll(dateOptions),
  };
},

Я знаю, что цепочка событий - это антипроцессор Flux, но я не уверен, какая архитектура лучше для получения данных, когда она еще не существует. В настоящее время я использую этот ужасный взлом:

getAll: function(options) {
  options = options || {};
  var key = JSON.stringify(options);
  var ratings = _data.ratings[key];

  if (!ratings) {
    setTimeout(function() {
      if (!RatingActions.dispatcher.isDispatching()) {
        RatingActions.fetchAll(options);
      }
    }, 0);
  }

  return ratings || [];
},

Что было бы лучшей архитектурой, позволяющей избегать цепочек событий или ошибки диспетчера? Это действительно цепочка событий? Я просто хочу изменить данные на основе параметров, которые приложение установило.

Спасибо!

Ответы

Ответ 1

Вы можете использовать Flux waitFor() вместо setTimeout

Например, у вас есть 2 магазина, зарегистрированные в одном диспетчере, и у них есть одно хранилище. Для другого хранилища сначала обработать действие, а затем ожидание может обновиться после и отправить событие изменения. См. Flux docs пример

Ответ 2

Моя особая ошибка происходила из-за того, что мои хранилища выбрали событие изменения во время отправки действия, в то время как все еще продолжалось циклическое прослушивание через слушателей. Это означало, что любые слушатели (т.е. компоненты), которые затем вызвали действие из-за изменения данных в магазине, прерывали отправку. Я исправил его, выпустив событие изменения после завершения отправки.

Итак, это:

this.emit(CHANGE_EVENT);

Стала

var self = this;

setTimeout(function() { // Run after dispatcher has finished
  self.emit(CHANGE_EVENT);
}, 0);

Еще немного взломанный (вероятно, переписать так, чтобы не требовалось setTimeout). Открыт для решений, которые решают архитектурную проблему, а не эту деталь реализации.

Ответ 3

Причина, по которой вы получаете отправку в середине предыдущей отправки, заключается в том, что ваш магазин отправляет действие (вызывает создателя действия) синхронно в обработчике для другого действия. Диспетчер технически отправляется до тех пор, пока не будут выполнены все его зарегистрированные обратные вызовы. Итак, если вы отправите новое действие из одного из зарегистрированных обратных вызовов, вы получите эту ошибку.

Однако, если вы выполняете какую-либо асинхронную работу, например. сделайте запрос ajax, вы все равно можете отправить действие в обратные вызовы ajax или обычный обратный вызов async. Это работает, поскольку, как только функция async вызывается, это определение сразу же продолжает выполнение функции и помещает обратный вызов в очередь событий.

Как указано Amida и в комментариях к этому ответу, вопрос о том, следует ли делать ajax-запросы из магазина в ответ на действие или делать это в магазине, является вопросом выбора. Ключ состоит в том, что хранилище должно только мутировать свое состояние в ответ на действие, а не в обратном вызове ajax/async.

В вашем конкретном случае это будет показано в качестве примера для зарегистрированного обратного вызова вашего магазина, если вы предпочитаете делать аякс-вызовы из магазина:

onGetAll: function(options) {
    // ...do some work

    request(ajaxOptions) // example for some promise-based ajax lib
        .then(function(data) {
             getAllSuccessAction(data); // run after dispatch
        })
        .error(function(data) {
             getAllFailedAction(data); // run after dispatch
        });

     // this will be immediately run during getAllAction dispatch
     return this.state[options];
},
onGetAllSuccess: function(data) {
    // update state or something and then trigger change event, or whatever
},    
onGetAllFailed: function(data) {
    // handle failure somehow
}

Или вы можете просто поместить вызов ajax в свой создатель действия и отправить оттуда действия "success/failed".

Ответ 4

В моем случае я получаю данные через создателей действий/действий. Магазин - это только место для дампа, которое получает полезную нагрузку для действия.

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

Некоторые люди считают использование таких магазинов, как я, другие думают, как вы. Некоторые люди в Facebook используют "мой" подход:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51

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

Интересная дискуссия такова: https://groups.google.com/forum/#!topic/reactjs/jBPHH4Q-8Sc

где Цзин Чен (инженер из Facebook) объясняет, что она думает о том, как использовать магазины.

Ответ 5

вы можете использовать опцию "Отложить" в диспетчере. В вашем случае это будет выглядеть так:

RatingActions.fetchAll.defer(options);