您好,我一直在研究 Redux,遇到了一个有趣的问题? 需要从其他动作发出一连串的异步请求
1-getUser()
2-getPost()
登录用户后执行 2 个解决方案 .then(dispatch({type:GET_POST_REQUEST}))
或者在middleWare中编写函数。
如何正确做?
你好! 这是一个问题跟踪器,而不是一个支持论坛。 感谢您下次在 StackOverflow 上提问,因为这里的答案会丢失,这与 SO 不同。
也就是说,如果您使用Redux Thunk中间件创建存储,您可以像这样编写异步操作创建器:
// If you use Redux Thunk...
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))
// You can define asynchronous action creators that return functions.
// We call such action creators "thunks":
export function getUser(id) {
// Redux Thunk will inject dispatch here:
return dispatch => {
// Reducers may handle this to set a flag like isFetching
dispatch({ type: 'GET_USER_REQUEST', id })
// Perform the actual API call
return fetchUser().then(
response => {
// Reducers may handle this to show the data and reset isFetching
dispatch({ type: 'GET_USER_SUCCESS', id, response })
},
error => {
// Reducers may handle this to reset isFetching
dispatch({ type: 'GET_USER_FAILURE', id, error })
// Rethrow so returned Promise is rejected
throw error
}
)
}
}
// Thunks can be dispatched, if Redux Thunk is applied,
// just like normal action creators:
store.dispatch(getUser(42));
// The return value of dispatch() when you dispatch a thunk *is*
// the return value of the inner function. This is why it's useful
// to return a Promise (even though it is not strictly necessary):
store.dispatch(getUser(42)).then(() =>
console.log('Fetched user and updated UI!')
)
// Here is another thunk action creator.
// It works exactly the same way.
export function getPost(id) {
return dispatch => {
dispatch({ type: 'GET_POST_REQUEST', id })
return fetchPost().then(
response => dispatch({ type: 'GET_POST_SUCCESS', id, response }),
error => {
dispatch({ type: 'GET_POST_FAILURE', id, error })
throw error
}
)
}
}
// Now we can combine them
export function getUserAndTheirFirstPost(userId) {
// Again, Redux Thunk will inject dispatch here.
// It also injects a second argument called getState() that lets us read the current state.
return (dispatch, getState) => {
// Remember I told you dispatch() can now handle thunks?
return dispatch(getUser(userId)).then(() => {
// Assuming this is where the fetched user got stored
const fetchedUser = getState().usersById[userId]
// Assuming it has a "postIDs" field:
const firstPostID = fetchedUser.postIDs[0]
// And we can dispatch() another thunk now!
return dispatch(getPost(firstPostID))
})
}
}
// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
console.log('fetched a user and their first post')
})
// We can do this anywhere we have access to dispatch().
// For example, we can use this.props.dispatch, or put action
// creators right into the props by passing them to connect, like this:
// export default connect(mapStateToProps, { getUserAndTheirFirstPost })
我们应该把它放到常见问题解答中。
我解决了这个问题如下。 无需修改操作。 我放入了组件 Promise。
clickShowUserEvent(data) {
Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
.then(function (response) {
data.showEvents(); //dispatch
return response;
})
.then(function(response){console.log("@RESPONSE",response);data.show(data)})
}
这是正确的决定吗?
这也有效,使用哪种模式取决于您。
@ar53n在 React 组件中带有 promise 的模式有几个缺陷:
catch
部分。 您可能会收到未经处理的拒绝。@sompylasar谢谢约翰谢谢你的评论。 我只是不想修改简单的动作。 我们有 2 个简单的操作Authentication
和GetEvents
这是 2 个异步请求,甚至包含catch
。 单击组件时仅调用此操作
例如
export function userAuth(login, password) {
return (dispatch, getState) => {
console.log('STATE', getState())
let newState = dispatch(requestUserAuth(login, password))
return fetch(AUTH_URL + newState.queryParams, MY_INIT)
.then(response => response.json())
.then(function (json) { dispatch(receiveUserAuth(json)); return json})
.catch(error => dispatch(errorUserAuth(error)))
}
}
我们有这个
如果我错了,请纠正我,谢谢
@ar53n那你很好,错误得到处理,并且在商店中跟踪过程。 组件中的不间断进程的问题仍然存在,但如果您在组件或操作重击中开始,这可能并不那么重要。
@ar53n你也可以看看redux-dataloader 。
它专为复杂的链异步操作而设计。
希望能帮助到你!
关闭它,因为这里发布了一些可能的解决方案。 您现在可能还想研究 redux-saga!
@gaearon你的例子是否打破了时间旅行,不是吗?
我的意思是,例如你的 AJAX 调用第一次失败,然后你去修复服务器端并想要重做它
@gaearon我尝试了您的解决方案,但是当我尝试从任何组件调用store.dispatch(...)
时(在我的情况下,从 LoginComponent 执行授权请求)我收到了这个错误:
undefined is not an object (evaluating '_AppNavigator.AppNavigator.router')
似乎有什么不对劲。 我已经像这样设置了动作创建者:
// actions.tsx
export const ActionCreators = {
authenticate: (username: string, password: string) => {
return (dispatch) => {
return auth.login(username, password).then(
response => {
dispatch(navActionCreators.login(res))
return response
},
error => {
throw error
}
)
}
}
}
// LoginScreen.tsx (login method)
store.dispatch(authActions.authenticate(this.state.username, this.state.password))
.then((res) => {
this.setState({isLoading: false})
})
.catch((error: Error) => {
this.setState({
isLoading: false,
error: error ? error.message : 'Si è verificato un\' errore.'
})
})
我错过了什么?
@bm-software:这个问题应该在 Stack Overflow 上提出。
@ar53n和@sompylasar我知道已经有一段时间了,但我现在正在努力解决这种模式。 @ar53n在您的示例中,如果userAuth
fetch
,那么在调用userAuth
的链中会发生什么?
看起来.catch(error => dispatch(errorUserAuth(error)))
会调度 errerUserAuth 操作,这很棒。 在 Redux 中,这通常是我们“处理”错误的方式。 但是在你之前提到的链条中:
clickShowUserEvent(data) {
Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
.then(function (response) {
data.showEvents(); //dispatch
return response;
})
.then(function(response){console.log("@RESPONSE",response);data.show(data)})
}
data.showEvents()
_总是_会被调用,即使用户身份验证失败。 我不认为这是大多数人所期望或想要的,但由于 Redux 错误处理通常是通过调度而不是重新抛出来完成的,它会吞下错误,因此承诺链无法按预期工作。
此外,如果您确实重新投掷,您将不得不在_应用程序中的每一个动作创建者调用,无处不在_上进行.catch()
。 上面@gaearon的示例,他重新抛出所有错误,最后这样做:
// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
console.log('fetched a user and their first post')
})
如果getUserAndTheirFirstPost
内的大组合链中的 _anything_ 失败,则会出现“未处理的承诺拒绝”错误。
我认为唯一的答案是重新抛出然后.catch()
到处,或者可能使用 React 16 错误边界。
@jasonrhodes
clickShowUserEvent(data) {
Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
.then(function (response) {
data.showEvents(); //dispatch
data.showEvents()
_always_ 将被调用,即使用户身份验证失败。
不,如果data.userAuth(data.login, data.password)
返回一个最终被拒绝的承诺,它将不会被调用。 .then
的第一个参数在 promise 完成时调用(称为onFulfilled
),第二个参数在 promise 被拒绝时调用(称为onRejected
)——参见规范.
另一方面,React 16 错误边界对 Promise 没有帮助,它们只捕获同步抛出的异常以确保 React 内部状态不被破坏。
@jasonrhodes另外,成为 Promise 世界的好公民,要么将 Promise 返回给调用者(然后它必须处理错误),要么将.catch
附加到它(并在创建 Promise 的地方处理错误)。
你的例子没有:
clickShowUserEvent(data) {
Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
.then(function (response) {
data.showEvents(); //dispatch
return response;
})
.then(function(response){console.log("@RESPONSE",response);data.show(data)})
}
@sompylasar这不是我的示例,它是您在此线程中早些时候一直在回复的示例。 我知道已经有一段时间了,但我在这里引用了之前的对话,因为我不止一次通过 Google 搜索遇到过这个帖子。
再看一遍,即使userAuth
fetch
调用失败, data.showEvents()
也会_总是_被调用。 为什么? 因为userAuth
函数内部有一个.catch()
以 Redux 方式处理错误:通过调度错误操作。
这就是我的帖子的重点:当您从异步操作中捕获错误以便可以分派错误操作时,您是否会吞下并阻止进一步的承诺链正常工作? 或者您是否重新抛出并强制该动作创建者的每个调用者.catch()
无论如何以避免“未处理的承诺拒绝”?
此外,根据我的经验,我发现在将某人链接到规范之前了解他们所知道的内容是个好主意。 感谢您的回复,我知道这是我挖掘的旧对话,但它很重要,因为您可以看到它是多么容易错过!
@jasonrhodes
@sompylasar这不是我的示例,它是您在此线程中早些时候一直在回复的示例。 我知道已经有一段时间了,但我在这里引用了之前的对话,因为我不止一次通过 Google 搜索遇到过这个帖子。
明白了,我很抱歉,没有回忆起整个上下文。
因为在
userAuth
函数内部有一个.catch()
以 Redux 方式处理错误:通过调度错误操作。
明白了,那么要么按预期工作,要么如果您返回该承诺(并在调用站点处理错误),则不应将.catch
放在那里,或者您应该在.catch
中重新抛出错误
无论如何,thunk 本身并不适合链接。 您应该在 thunk 中进行链接,或者将 sagas 用于更复杂的异步工作流。
我一直被打破承诺链的问题困扰了一段时间。 通常,我的动作创建者会在 .then() 中发出 SUCCESS 动作,或者在 .catch() 中发出 FAILURE 动作,该请求是从 thunk 返回的同一个 http 请求。
每当我的动作创建者进入 catch 块并且我执行了this.props.myActionCreator().then(() => )
时,即使请求中有问题,then 中的代码也会执行。
考虑到这一点,我总是确保检查应用程序状态中的错误变量,该变量在 FAILURE 情况下为该操作创建者设置。 但是当您调用多个动作创建者时,事情会变得一团糟,尤其是那些依赖于另一个的动作创建者。 我必须有一个 if 语句来检查许多错误变量。
我喜欢不通过在 catch 块中为动作创建者返回值重新抛出错误来破坏承诺链的事实。 但是,这需要我们使用 React 组件中的 .catch() ,其中调用了动作创建者。 我不会在那个 catch 块中写入任何内容,因为错误变量已经由减速器中的 FAILURE 操作的处理设置。
那么你们, @jasonrhodes , @sompylasar ,会推荐我使用重新抛出方法并在 React 组件中的动作创建者调用的承诺链上放置一个空的 .catch() 块吗?
@nbkhope老实说,这是我使用 Redux 时遇到的最大问题,直到今天我还没有找到一个好的答案。 很抱歉没有提供更多帮助!
伙计们,您可以在本文中找到一些 thunk 替代方案https://decembersoft.com/posts/what-is-the-right-way-to-do-asynchronous-operations-in-redux/
@gaearon
关于您的第一个答案,如果我使用 async & await 而不是 Promise,我应该如何转换它? 类似于以下内容:
export const funcA = () => {
return async (dispatch) => {
const data = await doSomething(...)
dispatch({ action: DID_SOMETHING, payload: data })
}
}
export const funcB = () => {
return async (dispatch) => {
const data = await doSomethingElse(...)
dispatch({ action: DID_SOMETHING_ELSE, payload: data })
}
}
export const funcC = () => {
return async (dispatch) => {
const data = await doSomethingMore(...)
dispatch({ action: DID_SOMETHING_MORE, payload: data })
}
}
// how to chain funcA, funcB and funcC
const myFunc = () => {
// execute funcA
// when complete execute funcB
// when complete execute funcC
}
@yingdongzhang ,您可以将它们链接如下:
const myFunc = () => {
return async (dispatch) => {
try {
await dispatch(funcA())
await dispatch(funcB())
await dispatch(funcC())
} catch (error) {
//error handling
}
}
}
@Boomxx谢谢你按预期工作。
我想知道我将如何做fetch all posts of the user
。
@km16 :这是一个错误跟踪器,而不是一个支持系统。 对于使用问题,请使用 Stack Overflow 或 Reactiflux,那里有更多的人准备帮助您 - 您可能会更快地得到更好的答案。 谢谢!
最有用的评论
你好! 这是一个问题跟踪器,而不是一个支持论坛。 感谢您下次在 StackOverflow 上提问,因为这里的答案会丢失,这与 SO 不同。
也就是说,如果您使用Redux Thunk中间件创建存储,您可以像这样编写异步操作创建器:
我们应该把它放到常见问题解答中。