简体   繁体   English

将参数从一个 Axios 请求传递给另一个

[英]Passing params from one Axios request to another

Background背景

I'm connecting an app built in React Native to a REST API.我正在将 React Native 中内置的应用程序连接到 REST API。 I'm handling requests via Axios and storing the results from queries with Redux.我正在通过 Axios 处理请求,并使用 Redux 存储查询的结果。 I have an index.js file for my api connections which holds the functions that act as handlers for requests which require deeper and deeper levels of authorization.我的 api 连接有一个 index.js 文件,该文件包含充当处理程序的函数,这些请求需要越来越深的授权级别。 I have a simple function which returns an access token, this is triggered by the following code which currenty is located in the app's "Welcome page".我有一个简单的 function 返回访问令牌,这是由当前位于应用程序的“欢迎页面”中的以下代码触发的。

useEffect(() => {
    dispatch(retrieveToken());
  }, [dispatch]);

Ideally, after navigating through a couple of screens, the user would get to the Home Page and trigger the following code:理想情况下,在浏览几个屏幕后,用户会到达主页并触发以下代码:

useEffect(() => {
    dispatch(retrieveData());
  }, [dispatch]);

So far, so good.到目前为止,一切都很好。 These are the functions which dispatch triggers:这些是调度触发器的函数:

 export const getToken = () =>
      apiInstance
        .request({
          url: ENDPOINTS.TOKEN,
          data: qs.stringify({
            grant_type: 'some_credentials',
            c_id: 'some_id',
            c_secret: 'some_secret',
          }),
          headers: {
            'content-type': 'some_content_type',
          },
          method: 'POST',
        })
        .then(response => {
          return response.data;
        })
        .catch(error => {
          return Promise.reject(error.message);
        });

export const getData = () =>
  apiInstance
    .request({
      url: ENDPOINTS.DATA,
      method: 'POST',
      data: qs.stringify({
        timestamp: Date.now(),
        c_id: 'some_id',
        token: **this is the token we get from the previous function**,
      }),
      headers: {
        'content-type': 'some_content_type',
      },
    })
    .then(response => {
      return response.data;
    })
    .catch(error => {
      return Promise.reject(error.message);
    });

Problem问题

As I mentioned before, this is a Redux/Axios solution.正如我之前提到的,这是一个 Redux/Axios 解决方案。 This means state is stored globally but there is an order of execution.这意味着 state 是全局存储的,但有执行顺序。 You should note that these two functions are stored within the same file and are not called upon unless explicitly stated such as with the two dispatch calls I've showed before.您应该注意,这两个函数存储在同一个文件中并且不会被调用,除非明确说明,例如我之前展示的两个调度调用。

Thing is, if I log the token from Home (after calling it with dispatch) I can see it clearly, however if I try to log said token from the file which stores the request functions, I get an empty array.问题是,如果我从 Home 记录令牌(在使用调度调用它之后),我可以清楚地看到它,但是如果我尝试从存储请求函数的文件中记录所述令牌,我会得到一个空数组。 I've tried to fill the token field in all the following ways:我尝试通过以下所有方式填写令牌字段:

  1. const state = store.getState() token: state.token常量 state = store.getState() 令牌:state.token
  2. const getData = (Token) =>{... token: Token} And passing Token as a param within dispatch. const getData = (Token) =>{... token: Token} 并在调度中将 Token 作为参数传递。
  3. I've also tried daisy-chaining the different dispatches in order to force the execution of getData after retrieving the token and not before.我还尝试以菊花链方式连接不同的调度,以便在检索令牌之后而不是之前强制执行 getData。

Question问题

How can I access the result of an axios query from within another, in specific order?如何按特定顺序从另一个内部访问 axios 查询的结果?

It is very important to note that the data in the API can only be accessed via POST and that the error code I get when executing getData() is 401, incorrect credentials.非常重要的是要注意 API 中的数据只能通过 POST 访问,并且我在执行 getData() 时得到的错误代码是 401,不正确的凭据。 Also, remember this is a Redux application.另外,请记住这是一个 Redux 应用程序。 The results of the queries are stored withing a global state.查询结果存储在全局 state 中。 However this state cannot be accessed from outside components, and I cannot call it from the file in which the queries are executed given the token "does not exist at that point in time."但是,无法从外部组件访问此 state,并且鉴于令牌“当时不存在”,我无法从执行查询的文件中调用它。

Action code动作代码

import keyMirror from 'keymirror';
import {createAction} from 'redux-actions';
import {getToken} from '../../api';

export const tokenActionTypes = keyMirror({
  RETRIEVE_TOKEN_REQUEST: null,
  RETRIEVE_TOKEN_SUCCESS: null,
  RETRIEVE_TOKEN_FAILURE: null,
});

const tokenActionCreators = {
  request: createAction(tokenActionTypes.RETRIEVE_TOKEN_REQUEST),
  success: createAction(tokenActionTypes.RETRIEVE_TOKEN_SUCCESS),
  failure: createAction(tokenActionTypes.RETRIEVE_TOKEN_FAILURE),
};

export const retrieveToken = () => dispatch => {
  dispatch(tokenActionCreators.request());
  getToken()
    .then(token => dispatch(tokenActionCreators.success(token)))
    .catch(error => dispatch(tokenActionCreators.failure(error)));
};

Reducer code减速机代码

import {tokenActionTypes} from '../actions/token';

export const initialState = {
  loadingToken: false,
  token: [],
  error: null,
};

const actionsMap = {
  [tokenActionTypes.RETRIEVE_TOKEN_REQUEST]: state => ({
    ...state,
    loadingToken: true,
  }),

  [tokenActionTypes.RETRIEVE_TOKEN_SUCCESS]: (state, action) => ({
    ...state,
    loadingToken: false,
    token: action.payload,
  }),

  [tokenActionTypes.RETRIEVE_TOKEN_FAILURE]: (state, action) => ({
    ...state,
    loadingToken: false,
    error: action.payload,
  }),
};

export default (state = initialState, action) => {
  const actionHandler = actionsMap[action.type];
  if (!actionHandler) {
    return state;
  }
  return actionHandler(state, action);
};

You could combine one thunk in another, like combining get token in get data:您可以将一个 thunk 组合在另一个中,例如将 get token 组合到 get data 中:

export const retrieveToken = () => (dispatch, getState) => {
  //you could use getState() to see if you need to fetch the token
  // const tokenResult = selectToken(getState());
  // if(token && !token expired) { return Promise.resolve() }
  dispatch(tokenActionCreators.request());
  //return a promise so you can wait for it
  return getToken()
    .then(token => dispatch(tokenActionCreators.success(token)))
    .catch(error => dispatch(tokenActionCreators.failure(error)));
};
//in retrieve data you can wait for the token
export const retrieveData = () => dispatch => {
  dispatch(retrieveToken()).then(
    ()=>{
      //here return getting the data
    }
  )
};

A possible bug in that code is that one render cycle will dispatch multiple thunks that will get the token multiple times.该代码中的一个可能错误是一个渲染周期将调度多个 thunk,这些 thunk 将多次获取令牌。 You can solve that by grouping the retrieveToken action with a cache that invalidates on resolve:您可以通过将retrieveToken 操作与在解析时失效的缓存分组来解决该问题:

const invalidateOnResolveCache = (cache = new Map()) => {
  return {
    get: (key) => cache.get(key),
    set: (key, value) => cache.set(key, value),
    resolved: (x) => cache.delete(key),
  };
};

Or you can write a wrap function for all thunks that need a token:或者,您可以为所有需要令牌的 thunk 编写包装 function:

//group retrieveToken in such a way that if it's called multiple times
//  during a render cycle the token request will only be made once
//https://gist.github.com/amsterdamharu/2dde4a6f531251f3769206ee44458af7
export const needsToken =
  (fn) =>
  (...args) =>
  (dispatch, getState) =>
    dispatch(retrieveToken(...args)).then(() =>
      //you could use getState to get the token and pass it to
      //  fn together with the other args
      // for example: fn(...args.concat(selectToken(getState())))
      fn(...args)
    );
export const autoTokenRetrieveData = needsToken(retrieveData);
//use needsToken for any other thunk actions that need a token

Example:例子:

 const { Provider, useDispatch, useSelector } = ReactRedux; const { createStore, applyMiddleware, compose } = Redux; const { createSelector } = Reselect; //grouping code to group your actions //group promise returning function const createGroup = (cache) => (fn, getKey = (...x) => JSON.stringify(x)) => (...args) => { const key = getKey(args); let result = cache.get(key); if (result) { return result; } //no cache result = Promise.resolve(fn.apply(null, args)).then( (r) => { cache.resolved(key); //tell cache promise is done return r; }, (e) => { cache.resolve(key); //tell cache promise is done return Promise.reject(e); } ); cache.set(key, result); return result; }; //thunk action creators are not (...args)=>result but // (...args)=>(dispatch,getState)=>result // so here is how we group thunk actions const createGroupedThunkAction = (thunkAction, cache) => { const group = createGroup(cache)( (args, dispatch, getState) => thunkAction.apply(null, args)(dispatch, getState) ); return (...args) => (dispatch, getState) => { return group(args, dispatch, getState); }; }; const createInvalidateOnResolveCache = ( cache = new Map() ) => { return { get: (key) => cache.get(key), set: (key, value) => cache.set(key, value), resolved: (key) => cache.delete(key), }; }; //function that fetches token const uniqueToken = ( (token) => () => token++ )(1); const fetchToken = () => Promise.resolve(uniqueToken()); const initialState = { data1: [], data2: [], token: null, }; //action types const DATA_SUCCESS = 'DATA_SUCCESS'; const GOT_TOKEN = 'GOT_TOKEN'; //action creators const dataSuccess = (data, key) => ({ type: DATA_SUCCESS, payload: { key, data }, }); const gotToken = (token) => ({ type: GOT_TOKEN, payload: token, }); const reducer = (state, { type, payload }) => { if (type === DATA_SUCCESS) { const { data, key } = payload; return {...state, [key]: data, }; } if (type === GOT_TOKEN) { return {...state, token: { value: payload, created: Date.now(), }, }; } return state; }; //thunk getting the data const getData1 = (token) => (dispatch) => Promise.resolve().then(() => dispatch( dataSuccess( `got data 1 with token: ${token}`, 'data1' ) ) ); const getData2 = (token) => (dispatch) => Promise.resolve().then(() => dispatch( dataSuccess( `got data 2 with token: ${token}`, 'data2' ) ) ); //thunk getting the token: const getToken = () => (dispatch) => fetchToken().then((token) => dispatch(gotToken(token))); //grouped thunk getting token const getTokenGrouped = createGroupedThunkAction( getToken, createInvalidateOnResolveCache() ); const needsToken = (fn) => (...args) => (dispatch, getState) => { let promise; //only fetch token if it's older than 1 second const tokenResult = selectToken(getState()); if ( tokenResult && Date.now() - new Date(tokenResult.created).getTime() < 1000 ) { promise = Promise.resolve(); } else { promise = dispatch(getTokenGrouped(...args)); } return promise.then(() => dispatch( fn(...args.concat(selectTokenValue(getState()))) ) ); }; const getData1WithToken = needsToken(getData1); const getData2WithToken = needsToken(getData2); //selectors const selectData1 = (state) => state.data1; const selectData2 = (state) => state.data2; const selectToken = (state) => state.token; const selectTokenValue = createSelector( [selectToken], //SO snippet has no optional chaining, should just return token?.value (token) => token && token.value ); //creating store with redux dev tools const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore( reducer, initialState, composeEnhancers( applyMiddleware( //simple thunk middleware ({ dispatch, getState }) => (next) => (action) => typeof action === 'function'? action(dispatch, getState): next(action) ) ) ); const Data1 = React.memo(function Data1({ refresh }) { const data = useSelector(selectData1); const dispatch = useDispatch(); React.useEffect(() => { dispatch(getData1WithToken()); }, [dispatch, refresh]); return <div>{data}</div>; }); const Data2 = React.memo(function Data2({ refresh }) { const data = useSelector(selectData2); const dispatch = useDispatch(); React.useEffect(() => { dispatch(getData2WithToken()); }, [dispatch, refresh]); return <div>{data}</div>; }); const App = () => { const [refresh, setRefresh] = React.useState({}); return ( <div> {/* getting data in one render cycle many times */} <Data1 refresh={refresh} /> <Data2 refresh={refresh} /> <Data1 refresh={refresh} /> <Data2 refresh={refresh} /> <Data1 refresh={refresh} /> <Data2 refresh={refresh} /> <button onClick={() => setRefresh({})}> refresh </button> </div> ); }; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script> <script src="https://unpkg.com/immer@7.0.5/dist/immer.umd.production.min.js"></script> <div id="root"></div>

Explanation:解释:

Everywhere you see const add export so export const or export default and you can import that from any other file.您在任何地方看到const add export所以export constexport default ,您可以从任何其他文件导入它。

The createGroupedThunkAction receives getToken thunk and returns a thunk that is stored in getTokenGrouped . createGroupedThunkAction接收getToken thunk 并返回存储在getTokenGrouped中的 thunk。

When getTokenGrouped is called multiple times during one render (Data1 and Data2 have an effect that will do so) it will share getting the token for that render and when it resolves it'll delete the cache because of the type of cache used implemented in createInvalidateOnResolveCache .当在一次渲染期间多次调用getTokenGrouped时(Data1 和 Data2 具有这样做的效果),它将共享获取该渲染的令牌,并且当它解析时,它将删除缓存,因为在createInvalidateOnResolveCache中实现了使用的缓存类型. So no multiple tokens will be fetched during one render no matter how many times you dispatch it during a render.因此,无论您在渲染期间调度多少次,都不会在一次渲染期间获取多个令牌。

The needsToken function will receive a thunk (getData1 and getData2) and returns a thunk that will automatically get a token by dispatching getTokenGrouped if there is no current token or if the token is older than one second (my made up logic to invalidate the token). needsToken function 将接收一个 thunk(getData1 和 getData2)并返回一个 thunk,如果没有当前令牌或令牌超过一秒(我编造的逻辑使令牌无效),它将通过调度getTokenGrouped自动获取令牌. This token is stored in state and passed to getData1 and getData2 so they can use that token.此令牌存储在 state 中并传递给getData1getData2 ,以便他们可以使用该令牌。

I suggest opening the redux devtools while running the example so you can see the actions being dispatched.我建议在运行示例时打开 redux 开发工具,以便您可以看到正在调度的操作。 Normally with async you would dispatch beforeFetch, afterFetch or faildFetch for async actions but for simplicity I left that out.通常使用 async 时,您会为异步操作分派 beforeFetch、afterFetch 或 faildFetch,但为简单起见,我将其省略了。

You could try to use createGroupedThunkAction to make a grouped getData1 and getData2 as an exercise so there is no needless fetching for these as well.您可以尝试使用createGroupedThunkActiongetData1getData2分组作为练习,因此也无需获取这些内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM