在Flux应用程序中应在何处提出Ajax请求?


Answers:


127

我强烈支持将异步写入操作放在动作创建者中,而将异步读取操作放在商店中。目的是将商店状态修改代码保留在完全同步的动作处理程序中;这使他们易于推理,并且易于进行单元测试。为了防止对同一个端点同时发出多个请求(例如,重复读取),我将把实际的请求处理移到一个单独的模块中,该模块使用promise来防止多个请求。例如:

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对我而言,最大的吸引力在于将所有状态更新都保留在同步代码路径中,并且明确地是由于动作分派而导致的,因此我避免了商店内部的异步。
米歇尔·提里2014年

1
@Federico我仍然不清楚“最佳”解决方案是什么。我一直在尝试这种策略来进行数据加载,同时计算未完成的异步请求的数量。不幸的flux是,它们是在构造之后注入到商店中的,所以没有什么好方法可以在initialize方法中获得动作。您可能会从Yahoo的等常通量库中找到一些好主意。这是Fluxxor v2应该支持的更好的东西。如果您想进一步聊聊,请随时给我发送电子邮件。
Michelle Tilley,2015年

1
data: result应该是data : data吧?没有result。将数据参数重命名为有效负载或类似方法可能更好。
oligofren 2015年

2
我发现这个旧线程非常有帮助-特别是Bill Fisher和Jing Chen的评论。这与@BinaryMuse提出的建议非常接近,只是在动作创建者中发生了分派。
菲律宾2015年

37

Fluxxor有一个与API进行异步通信的示例

这篇博客文章对此进行了讨论,并已在React的博客中进行了介绍。


我发现这是一个非常重要且困难的问题,目前尚未明确回答,因为前端软件与后端的同步仍然很痛苦。

是否应在JSX组件中提出API请求?商店?其他地方?

在商店中执行请求意味着,如果2个商店在给定操作中需要相同的数据,它们将发出2个类似的命令(除非您引入商店之间的依赖关系,我真的不喜欢

就我而言,我发现将Q许诺作为行动的有效载荷非常方便,因为:

  • 我的操作不需要可序列化(我不保留事件日志,不需要事件源的事件重播功能)
  • 它消除了具有不同操作/事件(触发请求/请求完成/请求失败)的需要,并且在可以触发并发请求时,不必使用相关ID进行匹配。
  • 它允许多个存储侦听同一请求的完成,而无需在存储之间引入任何依赖关系(但是引入缓存层可能更好?)

Ajax是邪恶的

我认为Ajax在不久的将来会越来越少使用,因为很难对此进行推理。正确的方式?考虑到设备是分布式系统的一部分,我不知道我最初是在哪里遇到这个想法的(也许是在这段鼓舞人心的Chris Granger视频中)。

想一想。现在,为了实现可伸缩性,我们使用具有最终一致性的分布式系统作为存储引擎(因为我们无法超越CAP定理,而且我们经常希望获得可用性)。这些系统不会通过互相轮询来进行同步(可能不是针对共识操作?),而是使用CRDT和事件日志之类的结构来使分布式系统的所有成员最终保持一致(给定的时间,成员将收敛到同一数据) 。

现在考虑什么是移动设备或浏览器。它只是分布式系统的成员,可能会遭受网络延迟和网络分区的影响。(即您在地铁上使用智能手机)

如果我们可以构建网络分区和网络速度容忍的数据库(我是说我们仍然可以对隔离的节点执行写操作),那么我们可能可以构建受这些概念启发的前端软件(移动或台式机),并且可以很好地支持离线模式没有应用功能的包装盒不可用。

我认为我们应该真正激发自己对数据库如何构建前端应用程序的灵感。需要注意的一件事是,这些应用程序不执行POST和PUT以及GET ajax请求来相互发送数据,而是使用事件日志和CRDT来确保最终的一致性。

那么为什么不在前端这样做呢?请注意,后端已经在朝这个方向发展,诸如Kafka之类的工具已被大型企业广泛采用。这也与事件来源/ CQRS / DDD相关。

查看来自Kafka作者的这些很棒的文章,以说服自己:

也许我们可以通过向服务器发送命令并接收服务器事件流(例如通过websocket)开始,而不是触发Ajax请求。

我从未对Ajax请求感到非常满意。正如我们所说,React开发人员往往是函数式程序员。我认为很难推断本地数据应该是您的前端应用程序的“事实来源”,而真实的真正来源实际上是在服务器数据库上,并且您的“本地”事实来源可能已经过时了当您收到它时,除非您按一些la脚的刷新按钮,否则它将永远不会收敛到真实值的真实来源……这是工程吗?

但是由于某些明显的原因,设计这样的东西仍然有点困难:

  • 您的移动/浏览器客户端资源有限,不一定在本地存储所有数据(因此有时需要使用ajax请求大量内容进行轮询)
  • 您的客户端不应看到分布式系统的所有数据,因此出于安全原因,它需要某种方式来过滤其接收的事件

3
您能否提供一个将Q承诺与行动结合使用的示例?
马特·福克斯

@MattFoxxDun无法确定这是一个好主意,因为它会使“事件日志”无法序列化,并使商店在被触发的动作上异步更新,因此它有一些缺点。但是,如果您的用例还可以,并且您了解这些缺点,那么它非常方便减少样板。使用Fluxxor,您可能可以做类似的事情this.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sebastien Lorber

完全不同意您的AJAX参数。实际上,阅读非常烦人。你看过你的话吗?想想赚钱的商店,游戏,应用程序-都需要API和AJAX服务器调用..如果您想要“无服务器”或类似性质的东西,请查看Firebase,但是AJAX在这里表示我希望至少没有其他人同意您的逻辑
TheBlackBenzKid

@TheBlackBenzKid我并不是说Ajax会在这一年中完全消失(并且确保我仍然作为创业公司的CTO仍在基于ajax请求的基础上构建网站),但是我说它很可能会消失,因为它的协议不够完善,无法处理最终的一致性,而是需要进行流传输而不是轮询,并且最终的一致性使应用程序可以可靠地脱机工作(是的,您可以自己用localstorage破解某些东西,但脱机能力有限,或者您的应用非常简单)。问题不在于缓存,它使该缓存无效。
塞巴斯蒂安·洛伯

@TheBlackBenzKid Firebase,Meteor等背后的模型不够好。您知道这些系统如何处理并发写入吗?最后写赢而不是因果关系/合并策略?当两个同事都在不可靠的连接上工作时,您能否负担您的同事在应用程序中的繁重工作?还要注意,这些系统往往会结合很多本地模型和服务器模型。您是否知道任何著名的协作应用程序都非常复杂,可以在脱机状态下完美运行,从而宣布自己是满意的Firebase用户?我不知道
塞巴斯蒂安·洛伯

20

您可以在操作创建者或商店中调用数据。重要的是不要直接处理响应,而要在错误/成功回调中创建一个动作。直接在商店中处理响应会导致设计更加脆弱。


9
您能详细解释一下吗?说我需要从服务器加载初始数据。在控制器视图中,我启动一个动作INIT,然后存储启动它的异步初始化,以反映该动作。现在,我会想到一个想法,即当商店获取数据时,它只会发出更改,而不会启动操作。因此,在初始化后发出更改会告诉视图它们可以从存储中获取数据。为什么有必要发射成功后加载的变化,但是从另一个动作?谢谢
Jim-Y

Fisherwebdev,关于商店调用数据,这样做,不要打破Flux范式,我想到的唯一两种调用数据的正确方法是使用:1.使用带有Actions的bootstrap类加载数据2视图,再次使用操作加载数据
Yotam 2015年

4
调用数据与接收数据不同。@ Jim-Y:您只应在存储中的数据实际更改后才发出更改。Yotam:不,在存储中调用数据不会破坏该范例。只能通过操作接收数据,以便所有存储都可以通过进入应用程序的任何新数据来通知。因此,我们可以在存储中调用数据,但是当响应返回时,我们需要创建一个新操作,而不是直接处理它。这使应用程序可以灵活地适应新功能的开发。
fisherwebdev

2

我一直在使用Fluxxor ajax example中的 Binary Muse的示例。这是我使用相同方法的非常简单的示例。

我有一个简单的产品存储区,其中包含一些产品操作controller-view组件,该组件具有子组件,这些子组件都可以响应对产品存储区所做的更改。例如product-sliderproduct-listproduct-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%同步。


2

我在这里回答了一个相关的问题:如何处理flux中的嵌套api调用

行动不应该是导致改变的事物。他们应该像报纸一样,将外部环境的变化通知应用程序,然后应用程序对该消息做出响应。商店本身会引起变化。行动只是告知他们。

Flux的创建者Bill Fisher 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
  }
}

0

这是我对此的看法:http : //www.thedreaming.org/2015/03/14/react-ajax/

希望能有所帮助。:)


8
根据指南进行投票。在外部站点上放置答案会使该站点的使用率降低,并导致质量较低的答案,从而降低了站点的实用性。外部网址也可能会及时中断。downvote没有说文章的用处,顺便说一句很好:)
oligofren 2015年

2
好的帖子,但是添加每种方法的优缺点的简短摘要将使您获得好评。在SO上,我们无需单击链接即可获得答案的依据。
Cory House
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.