在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 [] } }
内存泄漏核心原因
- 未正确等待IndexedDB事务完成:在
addData函数中,创建了读写事务并执行了put操作,但没有等待transaction.donePromise完成。idb库中,事务对象及其关联的资源(如数据引用、游标)只有在transaction.done完成后才会被标记为可回收。 - SWR自动刷新的频繁调用:SWR的自动刷新机制会反复调用
fetchAndSyncData,每次调用都会产生一个未完成的事务。这些未被清理的事务资源无法被垃圾回收(GC),持续累积后导致内存占用不断上升。 - 外部读取的差异:当把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
相关产品推荐
相关产品推荐

