Javascript 使用fetch api重新执行异步请求

Javascript 使用fetch api重新执行异步请求,javascript,fetch,race-condition,redux,es6-promise,Javascript,Fetch,Race Condition,Redux,Es6 Promise,我陷入了一种无法真正调试的古怪行为中 商店分派执行登录请求并传递用户名和密码的操作。然后,当响应就绪时,我将凭据存储在redux存储中。当我需要执行授权请求时,我在头请求中设置这些参数。当我收到响应时,我会使用从响应中获得的新凭据更新存储中的凭据。 当我尝试执行第三个请求时,它将响应未经授权的请求。我发现这是因为传递给action generator setCredentials的所有参数都为null。我不明白为什么,因为如果我在setCredentials函数的return语句之前添加一个调试

我陷入了一种无法真正调试的古怪行为中

商店分派执行登录请求并传递用户名和密码的操作。然后,当响应就绪时,我将凭据存储在redux存储中。当我需要执行授权请求时,我在头请求中设置这些参数。当我收到响应时,我会使用从响应中获得的新凭据更新存储中的凭据。 当我尝试执行第三个请求时,它将响应未经授权的请求。我发现这是因为传递给action generator 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)

当我问起这个问题时,我应该澄清一下

您是否验证了所有端点都返回这些头,而不仅仅是登录头?也许当您
performRequest(getUsers)
时,它会返回空标题

我不仅仅指服务器逻辑。我的意思是在DevTools中打开Network选项卡,并实际验证您的响应是否包含预期的标题。原来
getUsers()
标题并不总是包含凭据:

现在我们确认了这一点,让我们看看原因

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

  • 您可以启动
    submitLogin()
  • submitLogin()
    返回之前,您将启动
    performRequest(getUsers)
  • 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))
          },
        );
    }
    
    或者,您可以忽略减速器本身中的无效凭据:

    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))
    })
    

    如果这并不总是可行的,或者您想要更复杂的逻辑,比如重试失败的请求,我建议您看看哪种方法可以让您使用强大的并发原语来安排此类工作。

    您是否验证了所有端点都返回这些头,而不仅仅是登录头?也许当您
    performRequest(getUsers)
    时,它会返回空标题。是的。已证实的对我的后端的每个授权请求都会返回这些头@danAlas,很难从代码中猜到。如果你能创建一个单独的例子,在JSBin中重现这个问题,我可以看一看。非常感谢。我会的@丹希丹。我为测试创建了这个。感谢@DanAbramovI,我想这可能是问题所在,但我不明白为什么会发生这种情况,因为
    performRequest(getUsers)
    performRequest(getUsers)
    setTimeout
    函数中被触发。我认为登录请求应该在3秒钟内返回响应。无论如何,您认为这是管理凭据的好方法吗(我的意思是将它们保存在商店中)?非常感谢您抽出时间@danWell,依赖于在某段时间内出现的响应不是很明智,至少在生产代码中是这样:-)。你可以放更多的日志,检查我的诊断是否正确。不幸的是,我现在没有时间再次打开您的项目并为您执行:-)。是的,这个方法看起来不错。