React Native应用出现过多待处理回调警告,求排查方案
解决React Native中AsyncStorage引发的"Excessive number of pending callbacks"警告
问题描述
开发React Native应用时触发如下警告:
WARN Please report: Excessive number of pending callbacks: 501. Some pending callbacks that might have leaked by never being called from native code: {"88":{"module":"AsyncLocalStorage","method":"multiSet"},"89":{"module":"AsyncLocalStorage","method":"multiSet"},"90":{"module":"AsyncLocalStorage","method":"multiSet"},"91":{"module":"AsyncLocalStorage","method":"multiSet"},"92":{"module":"AsyncLocalStorage","method":"multiSet"},"94":{"module":"Networking","method":"sendRequest"},"19645":{},"19661":{},"19677":{},"19693":{},"19709":{},"19725":{},"19741":{},"19757":{},"19773":{},"19789":{},"19805":{},"19821":{},"19837":{},"19853":{},"19869":{},"19885":{},"19901":{},"19917":{},"19933":{},"19949":{},"19965":{},"19981":{},"19997":{},"20013":{},"20029":{},"20045":{},"20061":{},"20077":{},"20093":{},"20109":{},"20125":{},"20141":{},"20157":{},"20173":{},"20189":{},"20205":{},"20221":{},"20237":{},"20253":{},"20269":{},"20285":{},"20301":{},"20317":{},"20333":{},"...(truncated keys)...":451}
怀疑问题与AsyncStorage的使用有关,但无法定位具体触发点,以下是核心代码片段及修复方案。
问题根源
从警告和代码来看,核心问题是大量重复、无意义的AsyncStorage读写操作导致原生回调堆积,具体触发点包括:
- useEffect依赖项设置冗余,比如
saveShowXPhrases的依赖包含无关的showAllPhrases,导致无关状态变化时也触发存储 - 数组/对象类型的state(如
addedToX)每次修改生成新引用,引发useEffect频繁执行save - Navigation的
onStateChange直接调用存储,导航切换时短时间内产生大量请求 - 存储前未做值对比,相同内容重复写入AsyncStorage
修复方案
1. 清理useEffect依赖,去掉无关项
每个存储操作的useEffect只保留对应state作为依赖,避免无关状态变化触发不必要的存储。
2. 增加值对比,避免重复存储
封装通用存储函数,写入前先读取当前存储值,仅当内容不一致时才执行写入。
3. 合并初始化读取操作
将多个独立的AsyncStorage读取请求合并为一次并行请求,减少初始阶段的并发回调数量。
4. 导航状态存储添加防抖
对onStateChange的存储操作做防抖处理,避免短时间内多次触发存储。
优化后的代码示例
优化后的App.js
import React, { useContext, useEffect, useState, useCallback } from "react"; import { NavigationContainer } from "@react-navigation/native"; import AsyncStorage from "@react-native-async-storage/async-storage"; import Toast from "react-native-toast-message"; import toastConfig from "./App/config/toastSettings"; import phrases from "./App/data/phrases"; import navigationTheme from "./App/navigation/navigationTheme"; import AppNavigator from "./App/navigation/AppNavigator"; import FirstContext from "./App/context/context"; import savedData from "./App/utility/savedData"; import _ from "lodash"; export default function App() { const [isReady, setIsReady] = useState(false); const [initialState, setInitialState] = useState(); const [addedToX, setAddedToX] = useState(phrases); const [choseNumFromX, setChoseNumFromX] = useState(""); const [showXPhrases, setShowXPhrases] = useState(true); const [showAllPhrases, setShowAllPhrases] = useState(false); const [showBoth, setShowBoth] = useState(true); const [hideEng, setHideEng] = useState(false); const [hideJp, setHideJp] = useState(false); const [visibleEng, setVisibleEng] = useState(false); const [visibleJp, setVisibleJp] = useState(false); const PERSISTENCE_KEY = "NAVIGATION_STATE_V1"; // 合并所有初始化读取操作,减少并发请求 useEffect(() => { const initApp = async () => { try { const [ savedAddedToX, savedShowXPhrases, savedShowAllPhrases, savedShowBoth, savedHideEng, savedHideJp, navState ] = await Promise.all([ AsyncStorage.getItem("@addedToX"), AsyncStorage.getItem("@showXPhrases"), AsyncStorage.getItem("@showAllPhrases"), AsyncStorage.getItem("@showBoth"), AsyncStorage.getItem("@hideEng"), AsyncStorage.getItem("@hideJp"), AsyncStorage.getItem(PERSISTENCE_KEY) ]); setAddedToX(savedAddedToX ? JSON.parse(savedAddedToX) : phrases); setShowXPhrases(savedShowXPhrases ? JSON.parse(savedShowXPhrases) : true); setShowAllPhrases(savedShowAllPhrases ? JSON.parse(savedShowAllPhrases) : false); setShowBoth(savedShowBoth ? JSON.parse(savedShowBoth) : true); setHideEng(savedHideEng ? JSON.parse(savedHideEng) : false); setHideJp(savedHideJp ? JSON.parse(savedHideJp) : false); if (navState) setInitialState(JSON.parse(navState)); } catch (e) { console.error(e); } finally { setIsReady(true); } }; initApp(); }, []); // 仅在对应state变化时触发存储 useEffect(() => savedData.saveShowXPhrases(showXPhrases), [showXPhrases]); useEffect(() => savedData.saveShowAllPhrases(showAllPhrases), [showAllPhrases]); useEffect(() => savedData.saveShowBoth(showBoth), [showBoth]); useEffect(() => savedData.saveHideEng(hideEng), [hideEng]); useEffect(() => savedData.saveHideJp(hideJp), [hideJp]); useEffect(() => savedData.saveState(addedToX), [addedToX]); // 防抖处理导航状态存储,避免频繁触发 const debouncedSaveNavState = useCallback( _.debounce(async (state) => { await AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)); }, 500), [] ); if (!isReady) return null; return ( <FirstContext.Provider value={{ addedToX, setAddedToX, showXPhrases, setShowXPhrases, choseNumFromX, setChoseNumFromX, showAllPhrases, setShowAllPhrases, showBoth, setShowBoth, hideEng, setHideEng, hideJp, setHideJp, visibleEng, setVisibleEng, visibleJp, setVisibleJp, }} > <NavigationContainer initialState={initialState} onStateChange={(state) => debouncedSaveNavState(state)} > <AppNavigator /> <Toast config={toastConfig} /> </NavigationContainer> </FirstContext.Provider> ); }
优化后的savedData.js
import AsyncStorage from "@react-native-async-storage/async-storage"; import _ from "lodash"; // 通用存储函数:仅当值变化时才写入 const saveItem = async (key, value) => { try { const storedValue = await AsyncStorage.getItem(key); const parsedStored = storedValue ? JSON.parse(storedValue) : null; if (!_.isEqual(parsedStored, value)) { await AsyncStorage.setItem(key, JSON.stringify(value)); } } catch (error) { console.error(`保存${key}失败:`, error); } }; // 通用读取函数:读取失败时设置默认值 const getItem = async (key, defaultValue, setState) => { try { const storedValue = await AsyncStorage.getItem(key); setState(storedValue ? JSON.parse(storedValue) : defaultValue); } catch (error) { console.error(`读取${key}失败:`, error); setState(defaultValue); } }; // addedToX相关操作 const retrieveState = async (setAddedToX) => getItem("@addedToX", [], setAddedToX); const saveState = async (addedToX) => saveItem("@addedToX", addedToX); // 显示数量选项相关操作 const getShowXPhrases = async (setShowXPhrases) => getItem("@showXPhrases", true, setShowXPhrases); const saveShowXPhrases = async (showXPhrases) => saveItem("@showXPhrases", showXPhrases); const getShowAllPhrases = async (setShowAllPhrases) => getItem("@showAllPhrases", false, setShowAllPhrases); const saveShowAllPhrases = async (showAllPhrases) => saveItem("@showAllPhrases", showAllPhrases); // 语言显示选项相关操作 const getShowBoth = async (setShowBoth) => getItem("@showBoth", true, setShowBoth); const saveShowBoth = async (showBoth) => saveItem("@showBoth", showBoth); const getHideEng = async (setHideEng) => getItem("@hideEng", false, setHideEng); const saveHideEng = async (hideEng) => saveItem("@hideEng", hideEng); const getHideJp = async (setHideJp) => getItem("@hideJp", false, setHideJp); const saveHideJp = async (hideJp) => saveItem("@hideJp", hideJp); export default { retrieveState, saveState, getShowXPhrases, saveShowXPhrases, getShowAllPhrases, saveShowAllPhrases, getShowBoth, saveShowBoth, getHideEng, saveHideEng, getHideJp, saveHideJp, };
内容的提问来源于stack exchange,提问作者sahasrabhuja neuron
相关产品推荐
相关产品推荐

