Javascript 使用redux+;等待后台任务的惯用方法;雷迪克斯·图恩

Javascript 使用redux+;等待后台任务的惯用方法;雷迪克斯·图恩,javascript,reactjs,redux,react-redux,redux-thunk,Javascript,Reactjs,Redux,React Redux,Redux Thunk,在我的应用程序中,我们有一个运行时间较长的“资源调配”任务,它是异步完成的,我们必须使用轮询来检测它是否已经完成。因此,在这种情况发生时,用户不会被阻止使用该应用程序,我们将在用户交互流程开始时启动该应用程序。但是,在交互流结束时,用户执行一个操作,如果该操作尚未完成,则需要等待该配置任务。这里有一种序列图来说明 [provisioning start][poll for completion....] [user interactions] [blocked action....][

在我的应用程序中,我们有一个运行时间较长的“资源调配”任务,它是异步完成的,我们必须使用轮询来检测它是否已经完成。因此,在这种情况发生时,用户不会被阻止使用该应用程序,我们将在用户交互流程开始时启动该应用程序。但是,在交互流结束时,用户执行一个操作,如果该操作尚未完成,则需要等待该配置任务。这里有一种序列图来说明

[provisioning start][poll for completion....]
[user interactions]      [blocked action....][post provisioning task]
我遇到的问题是找到一种惯用的方法在Redux中正确地实现这一点。以下是我考虑过要做的一些事情:

  • 有一个“requests”reducer,在那里我将为长期运行的配置任务存储
    承诺
    ,然后当
    [阻止的操作]
    执行时,它只是等待承诺。问题是Redux特别要求所有存储状态都是可序列化的,所以这里似乎不欢迎承诺之类的东西
  • 订阅
    [blocked action]
    action creator中的存储,并查找表示资源调配已完成的数据。不幸的是,我非常喜欢的
    redux thunk
    ,它不包含对存储的引用,只包含它的
    dispatch
    getState
    方法
  • 让我的React组件在执行
    [阻止的操作]
    之前等待设置完成。我不喜欢这个,因为它给我的视图层增加了太多的动作顺序逻辑意识
  • 由于这些都不是很好的选择,我现在选择:

  • 在模块属性中存储设置
    Promise
    ,基本上执行选项#1,而不从存储中获取
    Promise
    。我对此并不感兴趣,因为它将状态转移到redux循环之外,但这似乎是最简单的选择

  • 有没有更惯用的方法来实现这一点?如果有另一个异步中间件使这个更干净,我愿意考虑,虽然我已经完全投资于代码> ReDuxthBoo./Calp>.< /P> < P>有几个Redix承诺中间件选项,允许您调度承诺动作,并使中间件消耗它们并发出待机,已解决|承诺状态更改时拒绝的操作。如果您使用的是promises和Redux,您可能需要查看其中一个。然而,他们不会帮助你解决这个具体问题

    我认为这是一个非常适合帮助您实现此场景的中间件。我还没有亲自使用过它,所以我不能提供一个例子

    另一个可能更简单的选择是。使用tap公开应用程序可以订阅的操作流。让阻止的操作创建者thunk订阅该流,并等待表示承诺已完成的操作(当然,它应该首先通过
    getState
    检查存储区的内容,以查看在发送此阻止的操作之前承诺是否已完成)。大概是这样的:

    // ========= configureStore.js
    import ee from 'event-emitter';
    
    // ...
    export const actionStream = ee();
    
    // ...
    const emitActions = tap(({type} => type, (type, action) => actionStream.emit(type, action);
    
    // install emitActions middleware to run *after* thunk (so that it does not see thunk actions but only primitive actions)
    
    
    // =========== your action module.js
    import {actionStream} from './configureStore';
    
    export function blockedAction(arg) {
        return (dispatch, getState) => {
            if (getState().initDone) {
                return dispatch({type: "blockedAction", payload: arg});
            }
    
            // wait for init action
            actionStream.once("initAction", initAction => dispatch({type: "blockedAction", payload: arg}));
        };
    }
    
    import { startJob } from 'redux-background';
    import store from './store';
    
    store.dispatch(startJob('provision', function(job, dispatch, getState) {
      const { progress, data } = job;
      return new Promise((resolve, reject) => {
        // Do some async stuff
        // Report progress
        progress(10);
    
        // Return value
        return 10;
      })
    }, { data: '...' } );
    
    请记住,它不是二进制的“thunk或xxx中间件”选择。您可以加载thunk以及许多其他中间件。在一个应用程序中,我使用:

    • 诺言
    • 控制台日志记录中间件
    • 用于批量更新的自定义中间件
    • 用于将操作持久化到IndexedDB的自定义中间件
    • 用于暂停操作流的自定义中间件
    • 防止递归存储通知的中间件
    没有thunk以外的任何中间件,这里有另一个选项:


    选项5-将选项2与选项4结合起来:将承诺存储在全球某个地方。让被阻止的动作创建者thunk在发送原始动作之前等待承诺。为了最小化“存储区外的状态”,您还可以让promise操作向存储区发出操作,以保持存储区的更新(可能使用promise中间件)

    我认为这更简单,除非我遗漏了一些东西,如果我们更严格地将UI视为状态的函数。在您的交互流程中的每一点上,都有一些事情是正确的或错误的:

  • 设置已启动/未启动
  • 设置已完成/未完成
  • 用户操作挂起/未挂起
  • 这三个事实足以告诉我们用户在给定时刻应该在屏幕上看到什么,因此我们的状态应该包括这三个事实:

    const initialState = {
      provisioningStarted: false,
      provisioningDone: false,
      laterActionIsPending: false,
      // ...
    };
    
    使用redux thunk,我们可以处理设置操作:

    function startProvisioning() {
      return dispatch => {
        dispatch({ type: START_PROVISIONING });
    
        pollProvisioning().then(postSetupInfo => {
          dispatch({ type: DONE_PROVISIONING, postSetupInfo });
        });
      };
    }
    
    …在我们的减速机中:

    function appReducer(state, action) {
      // ...
      switch (action.type) {
        case START_PROVISIONING:
          return { ...state, provisioningStarted: true };
        case DONE_PROVISIONING:
          return { ...state, provisioningDone: true, postSetupInfo: action.postSetupInfo };
        case LATER_ACTION_PENDING:
          return { ...state, laterActionIsPending: true };
        // ...
      }
    }
    
    正如我所说,这应该足以告诉我们用户应该看到什么:

    const Provisioner=({provisiongstarted,provisiongdone,/*…*/})=>{
    如果(供应启动){
    如果(侧向移动){
    if(provisioningDone){
    返回完成!这是您的信息:{postSetupInfo}

    ; } 返回设置…

    ; } 返回做动作; } 返回-开始供应; }; 导出默认连接(mapStateToProps、mapDispatchToProps)(Provisioner);
    不完全是redux thunk,但在功能上是等效的 . 我创建了一个包,以简化流程

    事情是这样的:

    // ========= configureStore.js
    import ee from 'event-emitter';
    
    // ...
    export const actionStream = ee();
    
    // ...
    const emitActions = tap(({type} => type, (type, action) => actionStream.emit(type, action);
    
    // install emitActions middleware to run *after* thunk (so that it does not see thunk actions but only primitive actions)
    
    
    // =========== your action module.js
    import {actionStream} from './configureStore';
    
    export function blockedAction(arg) {
        return (dispatch, getState) => {
            if (getState().initDone) {
                return dispatch({type: "blockedAction", payload: arg});
            }
    
            // wait for init action
            actionStream.once("initAction", initAction => dispatch({type: "blockedAction", payload: arg}));
        };
    }
    
    import { startJob } from 'redux-background';
    import store from './store';
    
    store.dispatch(startJob('provision', function(job, dispatch, getState) {
      const { progress, data } = job;
      return new Promise((resolve, reject) => {
        // Do some async stuff
        // Report progress
        progress(10);
    
        // Return value
        return 10;
      })
    }, { data: '...' } );
    
    当你的状态结束时,你应该有这样的东西

    {
      background: {
       provision: {
         active: true,
         running: false,
         value: 10,
         error: null,
         /* and lot of more metadata */
         ....
       },
      }
    }
    

    你的选项5和我所说的选项4差不多,现在看来效果不错。这个
    redux点击
    看起来不允许动作创建者订阅动作流,尽管对于应用级动作订阅来说,它看起来确实很神奇。谢谢你的回答。我添加了一个例子来说明我用
    点击
    的意思。我明白了,谢谢。到目前为止,我似乎需要订阅一个全球商店参考资料,或者等待一个全球承诺。在这里,承诺似乎是更简单的答案。但我确实看到你可以为thunks,所以也许动作流