我陷入了一种我无法调试的奇怪行为。
商店调度执行登录请求的操作,传递用户名和密码。然后,当响应准备就绪时,我将凭证存储在redux存储中。当我需要执行授权请求时,我在头请求中设置这些参数。当我收到响应时,我使用从响应中获得的新凭据更新商店中的凭据。 当我尝试执行第三个请求时,它将以未经授权的方式响应。我发现这是因为传递给我的动作生成器setCredentials的所有参数都是null。我也无法理解为什么,因为如果我在setCredentials函数的return语句之前添加调试器并且在重新开始执行之前等待几秒钟,我发现参数不再是null。我正在考虑这样一个事实,即请求是异步的但是在then语句中,响应应该准备好了吗?我还注意到fetch为每个请求发送了两个请求。 这里的代码更清晰。
import { combineReducers } from 'redux'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const initialState = {
currentUser: {
credentials: {},
user: {}
},
test: {},
users: []
}
export const SUBMIT_LOGIN = 'SUBMIT_LOGIN'
export const SET_USER = 'SET_USER'
export const TEST = 'TEST'
export const SET_USERS = 'SET_USERS'
export const SET_CREDENTIALS = 'SET_CREDENTIALS'
//actions
const submitLogin = () => (dispatch) => {
return postLoginRequest()
.then(response => {
dispatch(setCredentials(
response.headers.get('access-token'),
response.headers.get('client'),
response.headers.get('expiry'),
response.headers.get('token-type'),
response.headers.get('uid')
));
return response
})
.then(response => {
return response.json();
})
.then(
(user) => dispatch(setUser(user.data)),
);
}
const performRequest = (api) => (dispatch) => {
return api()
.then(response => {
dispatch(setCredentials(
response.headers.get('access-token'),
response.headers.get('client'),
response.headers.get('expiry'),
response.headers.get('token-type'),
response.headers.get('uid')
));
return response
})
.then(response => {return response.json()})
.then(
(users) => {
dispatch(setUsers(users.data))
},
);
}
const setUsers = (users) => {
return {
type: SET_USERS,
users
}
}
const setUser = (user) => {
return {
type: SET_USER,
user
}
}
const setCredentials = (
access_token,
client,
expiry,
token_type,
uid
) => {
debugger
return {
type: SET_CREDENTIALS,
credentials: {
'access-token': access_token,
client,
expiry,
'token-type': token_type,
uid
}
}
}
//////////////
const currentUserInitialState = {
credentials: {},
user: {}
}
const currentUser = (state = currentUserInitialState, action) => {
switch (action.type) {
case SET_USER:
return Object.assign({}, state, {user: action.user})
case SET_CREDENTIALS:
return Object.assign({}, state, {credentials: action.credentials})
default:
return state
}
}
const rootReducer = combineReducers({
currentUser,
test
})
const getAuthorizedHeader = (store) => {
const credentials = store.getState().currentUser.credentials
const headers = new Headers(credentials)
return headers
}
//store creation
const createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore);
const store = createStoreWithMiddleware(rootReducer);
const postLoginRequest = () => {
return fetch('http://localhost:3000/auth/sign_in', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'test@test.com',
password: 'password',
})
})
}
const getUsers = () => {
const autorizedHeader = getAuthorizedHeader(store)
return fetch('http://localhost:3000/users',
{
method: 'GET',
headers : autorizedHeader
}
)
}
const getWorks = () => {
const autorizedHeader = getAuthorizedHeader(store)
return fetch('http://localhost:3000/work_offers',
{
method: 'GET',
headers : autorizedHeader
}
)
}
// this request works fine
store.dispatch(submitLogin())
// this request works fine
setTimeout(() => {
store.dispatch(performRequest(getUsers))
}, 3000)
// this fails
setTimeout(() => {
store.dispatch(performRequest(getWorks))
}, 5000)
答案 0 :(得分:2)
当我问
时,我应该澄清一下您是否确认所有端点都返回了这些标头而不仅仅是登录标头?也许当你
performRequest(getUsers)
时,它会返回空标题。
我不仅仅意味着服务器逻辑。我的意思是在DevTools中打开“网络”选项卡,实际上验证您的回复是否包含您期望的标题。事实证明getUsers()
标题并不总是包含凭据:
现在我们确认了这种情况,让我们看看为什么。
您同时发送submitLogin()
和performRequest(getUsers)
大致。在再现错误的情况下,问题在于以下步骤序列:
submitLogin()
performRequest(getUsers)
返回submitLogin()
submitLogin()
返回并存储响应标头中的凭据performRequest(getUsers)
返回但是,因为它在凭据可用之前启动,服务器以空标头响应,并且存储那些空凭据而不是现有凭证 performRequest(getWorks)
醇>
此问题有几种解决方法。
我不认为用空的覆盖现有的良好凭证是否真的有意义,是吗?在发送之前,您可以在performRequest
中检查它们是非空的:
const performRequest = (api) => (dispatch, getState) => {
return api()
.then(response => {
if (response.headers.get('access-token')) {
dispatch(setCredentials(
response.headers.get('access-token'),
response.headers.get('client'),
response.headers.get('expiry'),
response.headers.get('token-type'),
response.headers.get('uid')
));
}
return response
})
.then(response => {return response.json()})
.then(
(users) => {
dispatch(setUsers(users.data))
},
);
}
或者,您可以忽略reducer本身中的无效凭据:
case SET_CREDENTIALS:
if (action.credentials['access-token']) {
return Object.assign({}, state, {credentials: action.credentials})
} else {
return state
}
这两种方式都很好,取决于对你更有意义的惯例。
在任何情况下,你真的想在获得凭据之前解雇getUsers()
吗?如果没有,请仅在凭据可用之前触发请求。像这样:
store.dispatch(submitLogin()).then(() => {
store.dispatch(performRequest(getUsers))
store.dispatch(performRequest(getWorks))
})
如果它不总是可行或者您想要更复杂的逻辑,例如重试失败的请求,我建议您查看Redux Saga,它允许您使用强大的并发原语来安排此类工作。