Redux 和 isFetching 中的顺序异步操作
Sequential async actions in Redux and isFetching
我的React组件需要异步获取一些数据A,然后根据其内容,发送第二个异步请求获取数据B。所有结果都存储在Redux中,我们使用Redux-thunk。
页面上可能同时有多个组件都需要A,所以很有可能它已经存在于Redux中;但另一个组件也可以 获取 它,然后 isFetching
为真。我不想有多个相同的请求(尤其是因为浏览器取消了它们)。
像 https://github.com/reactjs/redux/issues/1676 and https://github.com/reactjs/redux/issues/723 这样的顺序操作的解决方案提出了一个 redux-thunk,它 return 是一个 promise,如果对象已经存在就已经解决了;例如:
function getA(uuid) {
return (dispatch, getState) => {
const currentA = getState().a[uuid];
if (currentA) {
// Return resolved promise with the already existing object
return Promise.resolve(currentA);
} else {
// Return async promise
return goFetchA(uuid).then(objectA => {
dispatch(receivedA(uuid, objectA));
return objectA;
});
}
};
}
function getAthenB(uuidA, uuidB) {
return dispatch =>
dispatch(getA(uuidA)).then(
objectA => dispatch(getB(objectA, uuidB)));
}
到目前为止,还不错。但是,如果状态同时包含对象和 'isFetching' 布尔值,我可以 return 做出什么样的承诺?如果我们可以将请求的实际 Promise 存储在状态中,这将是微不足道的,但那种事情不应该进入 Redux 状态。
function getA(uuid) {
return (dispatch, getState) => {
const currentA = getState().a[uuid];
if (currentA) {
if (!currentA.isFetching) {
return Promise.resolve(currentA.data);
} else {
// WHAT TO RETURN HERE?
}
} else {
dispatch(startFetchingA(uuid));
return goFetchA(uuid).then(objectA => {
receivedObjectA(uuid, objectA);
return objectA;
});
}
};
}
当我想取消正在进行的请求时,存在类似的问题 -- 它没有存储在任何地方,因此理想的解决方案也有助于解决这个问题。
我相信如果不引入一些可变的、不可序列化的存储是不可能处理这种情况的,它将存储 运行 请求,以及纯的、完全可序列化的 Redux 存储。
有很多库可以帮助你处理这种副作用(Redux Thunk 的Redux Saga and Redux Observable, to name a few). But if your case is limited to what you have just described, I would suggest using extra argument feature,我相信它是专门为这种情况设计的。
如何使用额外参数执行任务的快速概述:
/**
* init.js
*/
const api = ...; // creating api object here
const requests = {};
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({api, requests}))
)
/**
* actions.js
*/
function getA(uuid) {
return (dispatch, getState, {api, requests}) => {
if (requests[uuid]) return requests[uuid];
const currentA = getState().a[uuid];
if (currentA) {
return Promise.resolve(currentA.data);
}
dispatch(startFetchingA(uuid));
const request = requests[uuid] = api.fetchA(uuid);
return request.then(objectA => {
delete requests[uuid];
receivedObjectA(uuid, objectA);
return objectA;
});
};
}
可以使用相同的技术来取消请求。
此外,您可能会通过引入 custom Redux middleware 得到更简洁的解决方案,但这实际上取决于您真正成熟的案例是什么,而不是这个简化的案例。
我的React组件需要异步获取一些数据A,然后根据其内容,发送第二个异步请求获取数据B。所有结果都存储在Redux中,我们使用Redux-thunk。
页面上可能同时有多个组件都需要A,所以很有可能它已经存在于Redux中;但另一个组件也可以 获取 它,然后 isFetching
为真。我不想有多个相同的请求(尤其是因为浏览器取消了它们)。
像 https://github.com/reactjs/redux/issues/1676 and https://github.com/reactjs/redux/issues/723 这样的顺序操作的解决方案提出了一个 redux-thunk,它 return 是一个 promise,如果对象已经存在就已经解决了;例如:
function getA(uuid) {
return (dispatch, getState) => {
const currentA = getState().a[uuid];
if (currentA) {
// Return resolved promise with the already existing object
return Promise.resolve(currentA);
} else {
// Return async promise
return goFetchA(uuid).then(objectA => {
dispatch(receivedA(uuid, objectA));
return objectA;
});
}
};
}
function getAthenB(uuidA, uuidB) {
return dispatch =>
dispatch(getA(uuidA)).then(
objectA => dispatch(getB(objectA, uuidB)));
}
到目前为止,还不错。但是,如果状态同时包含对象和 'isFetching' 布尔值,我可以 return 做出什么样的承诺?如果我们可以将请求的实际 Promise 存储在状态中,这将是微不足道的,但那种事情不应该进入 Redux 状态。
function getA(uuid) {
return (dispatch, getState) => {
const currentA = getState().a[uuid];
if (currentA) {
if (!currentA.isFetching) {
return Promise.resolve(currentA.data);
} else {
// WHAT TO RETURN HERE?
}
} else {
dispatch(startFetchingA(uuid));
return goFetchA(uuid).then(objectA => {
receivedObjectA(uuid, objectA);
return objectA;
});
}
};
}
当我想取消正在进行的请求时,存在类似的问题 -- 它没有存储在任何地方,因此理想的解决方案也有助于解决这个问题。
我相信如果不引入一些可变的、不可序列化的存储是不可能处理这种情况的,它将存储 运行 请求,以及纯的、完全可序列化的 Redux 存储。
有很多库可以帮助你处理这种副作用(Redux Thunk 的Redux Saga and Redux Observable, to name a few). But if your case is limited to what you have just described, I would suggest using extra argument feature,我相信它是专门为这种情况设计的。
如何使用额外参数执行任务的快速概述:
/**
* init.js
*/
const api = ...; // creating api object here
const requests = {};
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({api, requests}))
)
/**
* actions.js
*/
function getA(uuid) {
return (dispatch, getState, {api, requests}) => {
if (requests[uuid]) return requests[uuid];
const currentA = getState().a[uuid];
if (currentA) {
return Promise.resolve(currentA.data);
}
dispatch(startFetchingA(uuid));
const request = requests[uuid] = api.fetchA(uuid);
return request.then(objectA => {
delete requests[uuid];
receivedObjectA(uuid, objectA);
return objectA;
});
};
}
可以使用相同的技术来取消请求。
此外,您可能会通过引入 custom Redux middleware 得到更简洁的解决方案,但这实际上取决于您真正成熟的案例是什么,而不是这个简化的案例。