[英]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:我尝试通过以下所有方式填写令牌字段:
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 const
或export 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 中并传递给getData1
和getData2
,以便他们可以使用该令牌。
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.您可以尝试使用createGroupedThunkAction
将getData1
和getData2
分组作为练习,因此也无需获取这些内容。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.