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中正确地实现这一点。以下是我考虑过要做的一些事情:
承诺
,然后当[阻止的操作]
执行时,它只是等待承诺。问题是Redux特别要求所有存储状态都是可序列化的,所以这里似乎不欢迎承诺之类的东西[blocked action]
action creator中的存储,并查找表示资源调配已完成的数据。不幸的是,我非常喜欢的redux thunk
,它不包含对存储的引用,只包含它的dispatch
和getState
方法[阻止的操作]
之前等待设置完成。我不喜欢这个,因为它给我的视图层增加了太多的动作顺序逻辑意识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的自定义中间件
- 用于暂停操作流的自定义中间件
- 防止递归存储通知的中间件
选项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,所以也许动作流