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

时间:2021-06-07 13:36:14

标签: javascript react-native rest redux axios

背景

我正在将一个内置于 React Native 的应用程序连接到 REST API。我正在通过 Axios 处理请求并使用 Redux 存储查询结果。我有一个用于我的 api 连接的 index.js 文件,其中包含充当需要更深层次授权的请求的处理程序的函数。我有一个返回访问令牌的简单函数,这是由当前位于应用程序“欢迎页面”中的以下代码触发的。

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

理想情况下,在浏览几个屏幕后,用户将进入主页并触发以下代码:

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

到目前为止,一切都很好。这些是调度触发器的函数:

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

问题

正如我之前提到的,这是一个 Redux/Axios 解决方案。这意味着状态是全局存储的,但有一个执行顺序。您应该注意到这两个函数存储在同一个文件中并且不会被调用,除非明确说明,例如我之前展示的两个调度调用。

事实是,如果我从 Home 记录令牌(在使用调度调用它之后),我可以清楚地看到它,但是如果我尝试从存储请求函数的文件中记录所述令牌,我会得到一个空数组。我尝试通过以下所有方式填充令牌字段:

  1. const state = store.getState() 令牌:state.token
  2. const getData = (令牌) =>{ ... 令牌:令牌} 并在 dispatch 中将 Token 作为参数传递。
  3. 我还尝试菊花链不同的分派以强制执行 getData 在检索令牌之后而不是之前。

问题

如何以特定顺序从另一个内部访问 axios 查询的结果?

<块引用>

需要注意的是,API 中的数据只能通过 POST 访问,并且我在执行 getData() 时得到的错误代码是 401,不正确的凭据。 另外,请记住这是一个 Redux 应用程序。查询的结果与全局状态一起存储。但是,无法从外部组件访问此状态,并且我无法从执行查询的文件中调用它,因为令牌“在那个时间点不存在。”

操作代码

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 代码

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

1 个答案:

答案 0 :(得分:1)

您可以将一个 thunk 组合到另一个中,例如在获取数据中组合获取令牌:

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

该代码中可能存在的错误是一个渲染周期将分派多个 thunk,这些 thunk 将多次获取令牌。您可以通过 grouping 带有在解析时无效的缓存的retrieveToken 操作来解决该问题:

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

或者你可以为所有需要令牌的 thunk 编写一个 wrap 函数:

//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

示例:

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>

说明:

在您看到 const 的任何地方添加 export 所以 export constexport default,您可以从任何其他文件中导入它。

createGroupedThunkAction 接收 getToken thunk 并返回存储在 getTokenGrouped 中的 thunk。

在一次渲染期间多次调用 getTokenGrouped 时(Data1 和 Data2 具有这样做的效果),它将共享获取该渲染的令牌,当它解析时,它将删除缓存,因为createInvalidateOnResolveCache 中使用的缓存类型。因此,无论您在渲染期间分派多少次,都不会在一次渲染期间获取多个令牌。

needsToken 函数将接收一个 thunk(getData1 和 getData2)并返回一个 thunk,如果没有当前令牌或令牌早于一个,它将通过调度 getTokenGrouped 自动获取一个令牌第二个(我编造的使令牌无效的逻辑)。此令牌存储在 state 中并传递给 getData1getData2,以便他们可以使用该令牌。

我建议在运行示例时打开 redux devtools,以便您可以看到正在调度的操作。通常使用 async 时,您会为异步操作调度 beforeFetch、afterFetch 或 faildFetch,但为了简单起见,我省略了这一点。

您可以尝试使用 createGroupedThunkActiongetData1getData2 分组作为练习,这样也不会对这些进行不必要的提取。

相关问题