React Native中如何编写返回Async Storage值的方法?
问题:Redux切片中无法正确获取AsyncStorage里的userToken用于请求
我用Context API实现了登录认证,把userToken存在AsyncStorage里。现在要在Redux切片(DataSlice.js)里获取这个access token发起服务器请求,但定义的getAccessToken方法不能正确返回token值。
AuthContext.js
import React, { createContext } from "react"; import { useState, useEffect } from "react"; import { Alert } from "react-native"; import AsyncStorage from '@react-native-async-storage/async-storage'; import NetInfo from "@react-native-community/netinfo"; import { BASE_URL } from "../constants/Const"; export const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [isLoading, setIsLoading] = useState(false); const [userToken, setuserToken] = useState(null); const login = async (email, password) => { setIsLoading(true); fetch(`${BASE_URL}/sign_in`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: email, password: password, }) }) .then((res) => res.json()) .then((json) => { if (json.responseCode == 200) { try { setuserToken(json.responseData.access_token); AsyncStorage.setItem("userToken", json.responseData.access_token) } catch (error) { console.log("Error while saving user token data", userToken); } } else { Alert.alert('Invalid email or password.'); return; } }) .catch((error) => { console.error(error); }); setIsLoading(false); } const logout = async () => { setIsLoading(true); setuserToken(null); try { await AsyncStorage.removeItem("userToken"); } catch (error) { console.log("Error while removing data", error); } setIsLoading(false); } const isLoggenIn = async () => { try { setIsLoading(true); let userToken = await AsyncStorage.getItem("userToken"); setuserToken(userToken); setIsLoading(false); } catch (error) { console.log("Error retrieving data", error); } } const [connected, setConnected] = useState(true); useEffect(() => { isLoggenIn(); const unsubscribe = NetInfo.addEventListener(state => { setConnected(state.isConnected); }); return () => unsubscribe(); }, []) return ( <AuthContext.Provider value={{ login, logout, isLoading, userToken ,connected,}}> {children} </AuthContext.Provider> ) }
DataSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import { useState } from 'react'; import { Alert } from 'react-native'; import { BASE_URL } from '../constants/Const'; import AsyncStorage from '@react-native-async-storage/async-storage'; //user token function const getAcessToken = async () => { const token = await AsyncStorage.getItem('userToken'); return token; } var userToken = getAcessToken(); // get and delete method request options const getDeleteRequestOptions = (methodType) => { return { method: methodType, headers: { 'Content-Type': 'application/json', 'autherization': userToken } } } // save and edit method request options const saveEditRequestOptions = (methodType, data) => { console.log("img uri",data.imgUri) const imgData = new FormData(); imgData.append('first_name', data.firstName); imgData.append('last_name', data.lastName); imgData.append('phone', data.phoneNo); imgData.append('email', data.email); imgData.append('image', { uri: data.imgUri, name: 'image', type: 'image/jpg' }); return { method: methodType, headers: { 'Content-Type': 'multipart/form-data', 'autherization': userToken }, body: imgData }; }; // fetch data export const getData = createAsyncThunk('fetch', async ({pageNo,limit}) => { return fetch(`${BASE_URL}/list_profile?page_number=${pageNo}&limit=${limit}`, getDeleteRequestOptions('GET')) .then((response) => response.json()) .then((json) => { if(pageNo === 0 && limit === 0){ return {data:json.receive,fetchAllData:true}; } return {data:json.receive,fetchAllData:false}; }) .catch((error) => { console.error(error); }); }); // delete data export const deleteData = createAsyncThunk('delete', async ({id}) => { return fetch(`${BASE_URL}/delete_profile/${id}`, getDeleteRequestOptions('DELETE',userToken)) .then((res) => res.json()) .catch((error) => { console.error(error); }); }); // save data export const saveData = createAsyncThunk('save', async (data) => { return fetch(`${BASE_URL}/add_profile`, saveEditRequestOptions('POST', data)) .then((res) => res.json()) .then((json) => { if (json.responseCode === 211) { Alert.alert('Input Error', json.responseMessage, [ { text: "OK" } ]) return; } console.log("save responese message ", json.responseMessage); }) .catch((error) => { console.error(error); }); }); // edit data export const editData = createAsyncThunk('edit', async (data) => { return fetch(`${BASE_URL}/update_profile/${data.id}`, saveEditRequestOptions('PUT', data)) .then((res) => res.json()) .then((json) => { console.log("edit data response message ", json.responseMessage); }) .catch((error) => { console.error(error); }); }); const initialState = { masterData: [], filteredData: [], allData:[], imgurl: '', }; export const dataSlice = createSlice({ name: 'crud', initialState, reducers: { filterData: (state, action) => { state.filteredData = action.payload; }, selectedImage: (state, action) => { state.imgurl = action.payload; }, }, extraReducers: { [getData.pending]: (state, action) => { console.log('fetching data is pending'); }, [getData.fulfilled]: (state, action) => { console.log('data fetched successfully') if (!action.payload) { Alert.alert('Network error', 'Data Fetching is Failded Please try Again later.', [ { text: "OK" } ]) return; } if(action.payload.fetchAllData){ state.allData = action.payload.data; } state.masterData = action.payload.data; state.filteredData = action.payload.data; }, [getData.rejected]: (state, action) => { console.log('fetching request rejected'); }, [deleteData.pending]: (state, action) => { console.log('delete data is pending'); }, [deleteData.fulfilled]: (state, action) => { console.log('data deleted successfully'); }, [deleteData.rejected]: (state, action) => { console.log('data delete request rejected'); Alert.alert('Delete Data Failure', 'Deleting Data Failed. Please try Again later.', [ { text: "OK" } ]) }, [saveData.pending]: (state, action) => { console.log('saving data is pending'); }, [saveData.fulfilled]: (state, action) => { console.log('data saved successfully'); }, [saveData.rejected]: (state, action) => { console.log('data save request rejected'); Alert.alert('Data Save Failure', 'Data Save Failed. Please try Again later.', [ { text: "OK" } ]) }, [editData.pending]: (state, action) => { console.log('edit data is pending'); }, [editData.fulfilled]: (state, action) => { console.log('data edited successfully'); }, [editData.rejected]: (state, action) => { console.log('edit data request rejected'); Alert.alert('Data Edit Failure', 'Edit data Failed. Please try Again later.', [ { text: "OK" } ]) }, }, }) export const { filterData, selectedImage } = dataSlice.actions; export default dataSlice.reducer
错误原因
- async函数返回值类型错误:
getAcessToken是异步函数,直接赋值var userToken = getAcessToken()得到的是Promise对象,而非实际的token字符串,导致请求头的autherization字段无效。 - 请求选项初始化时机错误:
getDeleteRequestOptions和saveEditRequestOptions在模块加载时同步执行,此时token还未从AsyncStorage中获取完成,拿到的只是未resolved的Promise。
解决方案1:在Async Thunk中动态获取Token
将获取token的逻辑放到每个异步thunk内部,确保拿到实际token后再生成请求选项:
修改后的DataSlice.js关键代码
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import { Alert } from 'react-native'; import { BASE_URL } from '../constants/Const'; import AsyncStorage from '@react-native-async-storage/async-storage'; // 封装获取token的异步函数 const getAccessToken = async () => { try { return await AsyncStorage.getItem('userToken'); } catch (error) { console.error('获取token失败:', error); return null; } } // 动态生成GET/DELETE请求选项(需传入token) const getDeleteRequestOptions = (methodType, token) => { return { method: methodType, headers: { 'Content-Type': 'application/json', 'autherization': token } } } // 动态生成POST/PUT请求选项(需传入token和data) const saveEditRequestOptions = (methodType, data, token) => { console.log("img uri", data.imgUri) const imgData = new FormData(); imgData.append('first_name', data.firstName); imgData.append('last_name', data.lastName); imgData.append('phone', data.phoneNo); imgData.append('email', data.email); imgData.append('image', { uri: data.imgUri, name: 'image', type: 'image/jpg' }); return { method: methodType, headers: { 'autherization': token // FormData会自动处理Content-Type,无需手动设置 }, body: imgData }; } // 修改getData thunk:先获取token再发请求 export const getData = createAsyncThunk('fetch', async ({pageNo,limit}) => { const token = await getAccessToken(); if (!token) { Alert.alert('认证失败', '请重新登录'); throw new Error('无有效token'); } return fetch(`${BASE_URL}/list_profile?page_number=${pageNo}&limit=${limit}`, getDeleteRequestOptions('GET', token)) .then((response) => response.json()) .then((json) => { return pageNo === 0 && limit === 0 ? {data:json.receive,fetchAllData:true} : {data:json.receive,fetchAllData:false}; }) .catch((error) => { console.error(error); throw error; // 抛出错误触发rejected状态 }); }); // 同理修改deleteData、saveData、editData export const deleteData = createAsyncThunk('delete', async ({id}) => { const token = await getAccessToken(); if (!token) { Alert.alert('认证失败', '请重新登录'); throw new Error('无有效token'); } return fetch(`${BASE_URL}/delete_profile/${id}`, getDeleteRequestOptions('DELETE', token)) .then((res) => res.json()) .catch((error) => { console.error(error); throw error; }); }); export const saveData = createAsyncThunk('save', async (data) => { const token = await getAccessToken(); if (!token) { Alert.alert('认证失败', '请重新登录'); throw new Error('无有效token'); } return fetch(`${BASE_URL}/add_profile`, saveEditRequestOptions('POST', data, token)) .then((res) => res.json()) .then((json) => { if (json.responseCode === 211) { Alert.alert('Input Error', json.responseMessage, [{ text: "OK" }]) return; } console.log("save responese message ", json.responseMessage); return json; }) .catch((error) => { console.error(error); throw error; }); }); export const editData = createAsyncThunk('edit', async (data) => { const token = await getAccessToken(); if (!token) { Alert.alert('认证失败', '请重新登录'); throw new Error('无有效token'); } return fetch(`${BASE_URL}/update_profile/${data.id}`, saveEditRequestOptions('PUT', data, token)) .then((res) => res.json()) .then((json) => { console.log("edit data response message ", json.responseMessage); return json; }) .catch((error) => { console.error(error); throw error; }); }); // 其余slice代码保持不变
解决方案2:将token同步到Redux状态
利用AuthContext初始化时获取token的逻辑,将token同步到Redux状态,后续直接从状态中读取:
步骤1:在DataSlice中添加token状态和设置action
const initialState = { masterData: [], filteredData: [], allData:[], imgurl: '', userToken: null, // 添加token状态 }; export const dataSlice = createSlice({ name: 'crud', initialState, reducers: { filterData: (state, action) => { state.filteredData = action.payload; }, selectedImage: (state, action) => { state.imgurl = action.payload; }, setUserToken: (state, action) => { // 添加设置token的action state.userToken = action.payload; } }, // 其余extraReducers保持不变 }) export const { filterData, selectedImage, setUserToken } = dataSlice.actions;
步骤2:在App组件中同步token到Redux
import { useContext, useEffect } from 'react'; import { AuthContext } from './AuthContext'; import { useDispatch } from 'react-redux'; import { setUserToken } from './DataSlice'; function App() { const { userToken } = useContext(AuthContext); const dispatch = useDispatch(); useEffect(() => { if (userToken) { dispatch(setUserToken(userToken)); } }, [userToken, dispatch]); // 其余App组件代码 }
步骤3:在Async Thunk中从Redux状态取token
export const getData = createAsyncThunk('fetch', async ({pageNo,limit}, {getState}) => { const { userToken } = getState().crud; // 对应slice的key if (!userToken) { Alert.alert('认证失败', '请重新登录'); throw new Error('无有效token'); } return fetch(`${BASE_URL}/list_profile?page_number=${pageNo}&limit=${limit}`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'autherization': userToken } }) // 后续逻辑保持不变 });
注意事项
- 请求头字段名:当前代码用的是
autherization,标准HTTP认证头是Authorization,如果后端要求标准字段,记得修改,否则会导致认证失败。 - FormData的Content-Type:使用FormData时无需手动设置
multipart/form-data,React Native会自动生成包含boundary的正确头信息,手动设置反而可能引发错误。 - **错误
相关产品推荐
相关产品推荐

