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

在useSWR的fetcher中读取IndexedDB为何引发内存泄漏?

内存泄漏原因分析

问题场景

在useSWR的fetcher函数内读取IndexedDB存储的数据时,每次自动刷新都会导致内存占用持续上升;但如果在fetcher外部仅读取一次IndexedDB数据并传入fetcher,内存占用则不会增长。

依赖版本

react: 18.2.0
next: 14.0.1
node.js: 20.18.0
swr: 2.2.4
idb: 8.0.0

相关代码

indexedDB.ts

import { DBSchema, IDBPDatabase, openDB } from 'idb'
import { StoreKey, StoreNames, StoreValue } from 'idb/build/entry'

import { Data } from './types'

const DB_NAME = 'temp-indexed-database'

// Define database schema
interface IndexedDB extends DBSchema {
  myData: {
    key: string
    value: Data
    indexes: {
      transactTime: string
    }
  }
}

const getDB = (() => {
  let dbPromise: Promise<IDBPDatabase<IndexedDB>> | null = null

  return async () => {
    if (typeof window === 'undefined') return null
    if (!dbPromise) {
      dbPromise = openDB<IndexedDB>(DB_NAME)
    }
    return dbPromise
  }
})()


export const getAllData = async <S extends StoreNames<IndexedDB>>(
  storeName: S,
  query?: StoreKey<IndexedDB, S> | IDBKeyRange | null,
  count?: number
) => {
  const db = await getDB()
  if (!db) return []
  return db.getAll(storeName, query, count)
}

export const addData = async <S extends StoreNames<IndexedDB>>(
  storeName: S,
  data: StoreValue<IndexedDB, S> | StoreValue<IndexedDB, S>[],
  key?: StoreKey<IndexedDB, S> | IDBKeyRange
) => {
  const db = await getDB()
  if (!db) return
  const transaction = db.transaction(storeName, 'readwrite')
  const store = transaction.objectStore(storeName)
  if (Array.isArray(data)) {
    await Promise.all(data.map((item) => store.put(item, key)))
  } else {
    await store.put(data, key)
  }
}

hook.ts

import useSWR from 'swr'
import { useState, useEffect, useMemo } from 'react'
import { Data } from './types'
import { getAllData } from './indexedDB'
import { fetchAndSyncData } from './service'

export const useSWRData = (params) => {
  const [initialData, setInitialData] = useState<Data[]>([])

  useEffect(() => {
    getAllData('myData').then(setInitialData)
  }, [])

  const {
    data,
    isValidating,
    error,
  } = useSWR<Data[]>(
    { key: 'fetchData', params },
    fetchAndSyncData
  )

  return useMemo(
    () => ({
      data: data ?? initialData,
      isValidating,
      error,
    }),
    [data, initialData, isValidating, error]
  )
}

service.ts

import { getAllData, addData } from './indexedDB'

// 假设以下辅助函数已定义
const getLastDate = (data) => {}
const fetchData = async (opts) => []
const sortObjArrByKey = (arr, key) => arr

export const fetchAndSyncData = async (params: {params: AnyRecord}) => {
  try {
    const indexedData= await getAllData('myData')
    const lastDate = getLastDate(indexedData)
    const newData = await fetchData({ params: params.params, startDate: lastDate })
    if (newData.length > 0) {
      await addData('myData', newData)
    }
    return sortObjArrByKey(
      [...(newData ?? []), ...(indexedData ?? [])],
      'timestamp'
    )
  } catch (error) {
    console.error('Error fetching and syncing data:', error)
    return []
  }
}

内存泄漏核心原因

  1. 未正确等待IndexedDB事务完成:在addData函数中,创建了读写事务并执行了put操作,但没有等待transaction.done Promise完成。idb库中,事务对象及其关联的资源(如数据引用、游标)只有在transaction.done完成后才会被标记为可回收。
  2. SWR自动刷新的频繁调用:SWR的自动刷新机制会反复调用fetchAndSyncData,每次调用都会产生一个未完成的事务。这些未被清理的事务资源无法被垃圾回收(GC),持续累积后导致内存占用不断上升。
  3. 外部读取的差异:当把IndexedDB读取移到fetcher外部时,仅会执行一次事务操作,没有重复创建未完成的事务,因此不会产生资源累积,内存也就不会持续增长。

修复方案

修改addData函数,添加对transaction.done的等待:

export const addData = async <S extends StoreNames<IndexedDB>>(
  storeName: S,
  data: StoreValue<IndexedDB, S> | StoreValue<IndexedDB, S>[],
  key?: StoreKey<IndexedDB, S> | IDBKeyRange
) => {
  const db = await getDB()
  if (!db) return
  const transaction = db.transaction(storeName, 'readwrite')
  const store = transaction.objectStore(storeName)
  if (Array.isArray(data)) {
    await Promise.all(data.map((item) => store.put(item, key)))
  } else {
    await store.put(data, key)
  }
  // 等待事务完成,确保资源可被GC回收
  await transaction.done
}

内容的提问来源于stack exchange,提问作者hyeogeon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 19:25:19