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

194

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

Енис Гюлек
источник

Ответы:

127

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

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

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

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

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
  }
}
Мишель Тилли
источник
Вы пытались поместить обещания в полезную нагрузку действия? Мне легче иметь дело, чем отправлять несколько действий
Себастьян Лорбер,
@SebastienLorber Самым большим преимуществом для меня является сохранение всех обновлений состояний в синхронном пути кода, и явно только в результате отправки действий, поэтому я избегаю асинхронности внутри хранилищ.
Мишель Тилли
1
@Federico Мне до сих пор неясно, каково «лучшее» решение. Я экспериментировал с этой стратегией загрузки данных в сочетании с подсчетом количества невыполненных асинхронных запросов. К сожалению flux, вводится в магазины после строительства, поэтому нет хорошего способа получить действия в методе инициализации. Вы можете найти несколько хороших идей из изоморфных потоков Yahoo; это то, что Fluxxor v2 должен поддерживать лучше. Не стесняйтесь, напишите мне, если вы хотите поговорить об этом больше.
Мишель Тилли
1
data: resultдолжно быть data : data, верно? нет нет result. возможно, лучше переименовать параметр данных в полезную нагрузку или что-то в этом роде.
oligofren
2
Я нашел эту старую ветку очень полезной - в частности, комментарии Билла Фишера и Цзин Чена. Это очень близко к тому, что предлагает @BinaryMuse, с небольшим отличием в том, что диспетчер происходит в создателе действия.
phillipwei
37

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

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


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

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

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

В моем случае мне показалось очень удобным помещать обещания Q в качестве полезных данных действий, потому что:

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

Аякс это ЗЛО

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

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

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

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

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

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

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

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

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

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

  • Ваш мобильный / браузерный клиент имеет ограниченные ресурсы и не обязательно может хранить все данные локально (таким образом, иногда требуется опрос с использованием контента с большим количеством запросов ajax)
  • Ваш клиент не должен видеть все данные распределенной системы, поэтому ему необходимо каким-то образом фильтровать события, которые он получает из соображений безопасности
Себастьян Лорбер
источник
3
Можете ли вы привести пример использования Q-обещаний с действиями?
Мэтт Фокс Дункан
@MattFoxxDuncan не уверен, что это хорошая идея, так как он делает «журнал событий» неосериализуемым и асинхронно обновляет хранилище при запускаемых действиях, поэтому у него есть некоторые недостатки. Однако, если это нормально для вашего варианта использования, и вы понимаете эти недостатки, это довольно удобно и уменьшить шаблон. С Fluxxor вы, вероятно, можете сделать что-то вродеthis.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Себастьян Лорбер
Полностью не согласен с вашим аргументом AJAX. На самом деле это было очень неприятно читать. Вы читали свои замечания? Подумайте о магазинах, играх, приложениях, которые приносят серьезные деньги - все требуют вызовов API и AJAX-серверов ... посмотрите на Firebase, если вы хотите "без сервера" или что-то в этом роде, но AJAX здесь, чтобы сказать, я надеюсь, что по крайней мере никто не согласится с ваша логика
TheBlackBenzKid
@TheBlackBenzKid Я не говорю, что Ajax полностью исчезнет в течение года (и, конечно, я все еще создаю веб-сайты на основе запросов AJAX в настоящее время как технический директор стартапа), но я говорю, что он, вероятно, исчезнет, ​​потому что это не достаточно хороший протокол для обработки возможной согласованности, которая скорее требует потоковой передачи, а не опроса, и возможная согласованность - это то, что позволяет приложениям работать надежно в автономном режиме (да, вы можете взломать что-нибудь с помощью localalstorage самостоятельно, но у вас будет ограниченная автономная емкость, или ваше приложение очень простое). Проблема не в кешировании, а в том, что этот кеш становится недействительным.
Себастьян Лорбер
@TheBlackBenzKid Модели Firebase, Meteor и т. Д. Недостаточно хороши. Знаете ли вы, как эти системы обрабатывают параллельные записи? последняя запись-победа вместо причинно-следственных связей / стратегий слияния? Можете ли вы позволить себе превосходную работу своего коллеги в приложении, когда оба работают над ненадежными соединениями? Также обратите внимание, что эти системы имеют тенденцию соединять локальную и серверную моделирование. Знаете ли вы какое-нибудь известное приложение для совместной работы, которое значительно сложнее, работает в автономном режиме, заявляя, что вы довольный пользователь Firebase? Я не
Себастьен Лорбер
20

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

fisherwebdev
источник
9
Можете ли вы объяснить это более подробно, пожалуйста? Скажем, мне нужно сделать начальную загрузку данных с сервера. В представлении контроллера я запускаю действие INIT, и Store начинает асинхронную инициализацию, отражающую это действие. Теперь я бы согласился с идеей, что когда Store получает данные, он просто генерирует изменения, но не запускает действие. Таким образом, передача изменений после инициализации говорит представлениям, что они могут получить данные из хранилища. Почему при успешной загрузке нужно не выдавать изменения, а начинать другое действие ?! Спасибо
Джим-Y
Fisherwebdev, о магазинах, требующих данных, тем самым не нарушайте парадигму Flux, единственные 2 правильных способа вызова данных, которые я могу придумать, - это использовать: 1. использовать класс начальной загрузки, используя Actions для загрузки данных 2 Представления, снова используя Действия для загрузки данных
Yotam
4
Запрос данных не то же самое, что получение данных. @ Джим-Y: вы должны генерировать изменения только после того, как данные в магазине действительно изменились. Йотам: Нет, обращение к данным в магазине не нарушает парадигму. Данные должны быть получены только через действия, так что все магазины могут быть проинформированы о любых новых данных, поступающих в приложение. Таким образом, мы можем запрашивать данные в хранилище, но когда ответ возвращается, нам нужно создать новое действие, а не обрабатывать его напрямую. Это делает приложение гибким и устойчивым к разработке новых функций.
fisherwebdev
2

Я использовал пример Binary Muse из примера Fluxor ajax . Вот мой очень простой пример, использующий тот же подход.

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

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

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

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% синхронными.

svnm
источник
2

Я ответил на связанный с этим вопрос здесь: Как обрабатывать вложенные вызовы API в потоке

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

Билл Фишер, создатель Flux https://stackoverflow.com/a/26581808/4258088

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

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

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

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
  }
}
MoeSattler
источник
0

Вот мой взгляд на это: http://www.thedreaming.org/2015/03/14/react-ajax/

Надеюсь, это поможет. :)

Джейсон Уолтон
источник
8
понижение в соответствии с руководящими принципами размещение ответов на внешних сайтах делает этот сайт менее полезным, а также снижает качество ответов и снижает полезность сайта. внешние URL, вероятно, тоже сломаются во времени. Даунвот ничего не говорит о полезности статьи, что, кстати, очень хорошо :)
oligofren
2
Хороший пост, но добавление краткого изложения плюсов / минусов каждого подхода принесет вам положительные отзывы. На SO нам не нужно нажимать на ссылку, чтобы получить суть вашего ответа.
Кори Дом