Где должен выполняться запрос ajax в приложении Flux?

Я создаю приложение response.js с архитектурой потока, и я пытаюсь выяснить, где и когда должен запрашиваться запрос данных с сервера. Есть ли какой-нибудь пример для этого. (Не приложение TODO!)

Ответ 1

Я большой сторонник размещения асинхронных операций записи в создателях действий и асинхронных операций чтения в хранилище. Цель состоит в том, чтобы сохранить код модификации состояния хранилища в полностью синхронных обработчиках действий; это делает их простыми рассуждать и просты до unit test. Чтобы предотвратить одновременное одновременное обращение к одной и той же конечной точке (например, двойное чтение), я переведу фактическую обработку запроса в отдельный модуль, который использует promises для предотвращения множественных запросов; например:

class MyResourceDAO {
  get(id) {
    if (!this.promises[id]) {
      this.promises[id] = new Promise((resolve, reject) => {
        // ajax handling here...
      });
    } 
    return this.promises[id];
  }
}

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

Например, компонент может сделать:

getInitialState() {
  return { data: myStore.getSomeData(this.props.id) };
}

В магазине будет реализован метод, возможно, что-то вроде этого:

class Store {
  getSomeData(id) {
    if (!this.cache[id]) {
      MyResurceDAO.get(id).then(this.updateFromServer);
      this.cache[id] = LOADING_TOKEN;
      // LOADING_TOKEN is a unique value of some kind
      // that the component can use to know that the
      // value is not yet available.
    }

    return this.cache[id];
  }

  updateFromServer(response) {
    fluxDispatcher.dispatch({
      type: "DATA_FROM_SERVER",
      payload: {id: response.id, data: response}
    });
  }

  // this handles the "DATA_FROM_SERVER" action
  handleDataFromServer(action) {
    this.cache[action.payload.id] = action.payload.data;
    this.emit("change"); // or whatever you do to re-render your app
  }
}

Ответ 2

Fluxxor имеет пример асинхронной связи с API.

Этот пост в блоге рассказывает об этом и был включен в блог React.


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

Должны ли выполняться запросы API в компонентах JSX? Магазины? Другое место?

Выполнение запросов в магазинах означает, что если 2 магазина нуждаются в одних и тех же данных для данного действия, они выдадут 2 похожих запроса (если вы не введете зависимости между хранилищами, которые я действительно надену, t нравится)

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

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

Ajax - это EVIL

Я думаю, что Ajax будет в меньшей степени использоваться в ближайшем будущем, потому что это очень сложно рассуждать. Правильный путь? Рассмотрение устройств как части распределенной системы Я не знаю, где я впервые встретил эту идею (возможно, в этом вдохновляющем видео Криса Грейнджера).

Подумайте об этом. Теперь для масштабируемости мы используем распределенные системы с возможной согласованностью как механизмы хранения (потому что мы не можем победить теорему CAP, и часто мы хотим быть доступными). Эти системы не синхронизируются путем опроса друг друга (за исключением, может быть, для консенсусных операций?), А используют такие структуры, как CRDT и журналы событий, чтобы сделать все члены распределенной системы в конечном итоге последовательными (члены будут сходиться к тем же данным, учитывая достаточное время).

Теперь подумайте о том, что такое мобильное устройство или браузер. Это просто член распределенной системы, который может страдать от сетевой задержки и сетевой разбивки. (т.е. Вы используете свой смартфон в метро)

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

Думаю, нам нужно действительно вдохновлять нас на то, как базы данных работают над архитектурой наших приложений frontend. Одно замечание состоит в том, что эти приложения не выполняют POST и PUT и GET ajax-запросы для отправки данных друг другу, а используют журналы событий и CRDT для обеспечения конечной согласованности.

Так почему бы не сделать это на фронте? Обратите внимание, что бэкэнд уже движется в этом направлении, с инструментами, такими как Kafka, широко принятыми крупными игроками. Это так или иначе связано с Event Sourcing/CQRS/DDD.

Проверьте эти удивительные статьи от авторов Kafka, чтобы убедить себя:

Возможно, мы можем начать с отправки команд на сервер и получения потока событий сервера (через веб-сайты для примера) вместо того, чтобы запускать запросы Ajax.

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

Однако по некоторым очевидным причинам еще немного сложно спроектировать:

  • Ваш клиент для мобильных/браузеров имеет ограниченные ресурсы и не может хранить все данные локально (поэтому иногда требуется опрос с тяжелым контентом ajax)
  • Ваш клиент не должен видеть все данные распределенной системы, поэтому он должен каким-то образом фильтровать события, которые он получает по соображениям безопасности.

Ответ 3

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

Ответ 4

Я использовал пример Binary Muse в примере Fluxxor ajax. Вот мой очень простой пример с использованием того же подхода.

У меня есть простой товарный магазин некоторые действия продукта и компонент controller-view, который имеет подкомпоненты, которые все реагируют на сделанные изменения в магазин продуктов. Например, компоненты product-slider, product-list и product-search.

Поддельный клиент продукта

Вот поддельный клиент, который вы можете заменить для вызова фактической конечной точки, возвращающей продукты.

var ProductClient = {

  load: function(success, failure) {
    setTimeout(function() {
      var ITEMS = require('../data/product-data.js');
      success(ITEMS);
    }, 1000);
  }    
};

module.exports = ProductClient;

Магазин продуктов

Вот Магазин товаров, очевидно, это очень минимальный магазин.

var Fluxxor = require("fluxxor");

var store = Fluxxor.createStore({

  initialize: function(options) {

    this.productItems = [];

    this.bindActions(
      constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
      constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
    );
  },

  onLoadSuccess: function(data) {    
    for(var i = 0; i < data.products.length; i++){
      this.productItems.push(data.products[i]);
    }    
    this.emit("change");
  },

  onLoadFail: function(error) {
    console.log(error);    
    this.emit("change");
  },    

  getState: function() {
    return {
      productItems: this.productItems
    };
  }
});

module.exports = store;

Теперь действия продукта, которые делают запрос AJAX и при успешном завершении действия LOAD_PRODUCTS_SUCCESS возвращают продукты в хранилище.

Действия продукта

var ProductClient = require("../fake-clients/product-client");

var actions = {

  loadProducts: function() {

    ProductClient.load(function(products) {
      this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
    }.bind(this), function(error) {
      this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
    }.bind(this));
  }    

};

module.exports = actions;

Таким образом, вызов this.getFlux().actions.productActions.loadProducts() из любого компонента, прослушивающего этот магазин, будет загружать продукты.

Вы можете представить себе разные действия, которые могли бы реагировать на взаимодействия с пользователем, такие как addProduct(id) removeProduct(id) и т.д.... по одному и тому же шаблону.

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

Ответ 5

Я ответил на соответствующий вопрос здесь: Как обрабатывать вложенные вызовы api в потоке

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

Билл Фишер, создатель Flux fooobar.com/questions/34681/...

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

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

Магазины могут выглядеть примерно так:

class DataStore {
  constructor() {
    this.data = [];

    this.bindListeners({
      handleDataNeeded: Action.DATA_NEEDED,
      handleNewData: Action.NEW_DATA
    });
  }

  handleDataNeeded(id) {
    if(neededDataNotThereYet){
      api.data.fetch(id, (err, res) => {
        //Code
        if(success){
          Action.newData(payLoad);
        }
      }
    }
  }

  handleNewData(data) {
    //code that saves data and emit change
  }
}