You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

错误原因

  1. async函数返回值类型错误:getAcessToken是异步函数,直接赋值var userToken = getAcessToken()得到的是Promise对象,而非实际的token字符串,导致请求头的autherization字段无效。
  2. 请求选项初始化时机错误: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
    }
  })
  // 后续逻辑保持不变
});

注意事项

  1. 请求头字段名:当前代码用的是autherization,标准HTTP认证头是Authorization,如果后端要求标准字段,记得修改,否则会导致认证失败。
  2. FormData的Content-Type:使用FormData时无需手动设置multipart/form-data,React Native会自动生成包含boundary的正确头信息,手动设置反而可能引发错误。
  3. **错误
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 23:50:35