javascript - 使用 fetch api Redux 异步请求

标签 javascript fetch race-condition redux es6-promise

我陷入了一种奇怪的行为,无法真正调试。

商店调度执行传递用户名和密码的登录请求的操作。然后,当响应准备好时,我将凭证存储在 redux 存储中。当我需要执行授权请求时,我在 header 请求中设置这些参数。当我收到响应时,我会使用从响应中获得的新凭据更新商店中的凭据。 当我尝试执行第三个请求时,它将未经授权地响应。我发现这是因为传递给我的操作生成器 setCredentials 的所有参数都是空的。我也不明白为什么,因为如果我在 setCredentials 函数的 return 语句之前添加一个调试器,并且在重新启动执行之前等待几秒钟,我发现参数不再为空。我在想请求是异步的,但在 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: '<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6e1a0b1d1a2e1a0b1d1a400d0103" rel="noreferrer noopener nofollow">[email protected]</a>',
      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)

最佳答案

当我问的时候我应该澄清一下

Have you verified that all your endpoints return those headers and not just the login one? Maybe when you performRequest(getUsers), it comes back with empty headers.

我指的不仅仅是服务器逻辑。我的意思是在 DevTools 中打开“网络”选项卡,并实际验证您的响应是否包含您期望的 header 。事实证明 getUsers() header 并不总是包含凭据:

现在我们确认了这种情况的发生,让我们看看为什么。

您大致同时调度 submitLogin()performRequest(getUsers)。在重现错误的情况下,问题出在以下步骤顺序中:

  1. 您触发submitLogin()
  2. 您在 submitLogin() 返回之前触发 performRequest(getUsers)
  3. submitLogin() 返回并存储响应 header 中的凭据
  4. performRequest(getUsers) 返回,但由于它在凭据可用之前启动,因此服务器以空 header 响应,并且存储这些空凭据而不是现有凭据
  5. performRequest(getWorks) 现在无需凭据即可请求

此问题有多种修复方法。

不要让旧的未经授权的请求覆盖凭据

我认为用空的凭证覆盖现有的良好凭证确实没有意义,不是吗?您可以在分派(dispatch)之前在 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它允许您使用强大的并发原语来安排此类工作。

关于javascript - 使用 fetch api Redux 异步请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35381276/

相关文章:

c - USB 音频缓冲区溢出

javascript - JavaScript 中的减法问题

javascript - insertRule 不适用于关键帧

c# - 添加到通用字典会导致 IndexOutOfRangeException

javascript - 如何创建带有 Content-Type header 的响应?

javascript - ASP.NET Core 获取 POST FormData() application/x-www-form-urlencoded

c - 模拟O_NOFOLLOW (2) : Is this other approach safe?

javascript - CSS/JQuery |过滤元素时,最后一个元素的一部分不会消失

javascript - Cookie 来保存多个值,包括一个变量?

react-native - 获取 someUrl.html 在 React Native/Expo 中不起作用?