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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 12:57:04