React Native文件下载触发RangeError: Property storage超限问题求助
解决React Native接收大Buffer时的
RangeError: Property storage exceeds 196607 properties问题 问题背景
开发文件存储应用时,文件以Buffer格式存储在MongoDB中;用户请求文件时,服务器返回对应Buffer数据,但React Native接收响应后抛出上述RangeError,导致应用崩溃或卡顿。
用户代码如下:
downloadFile 代码
import { Helpers } from '#/utils'; import { get } from 'lodash'; const downloadFile = async (fileDetails, isConnected, downloadFileMutation) => { const channelId = await Helpers.createChannelForProgressNotificationOfUploadingAndDownloadingFiles(); const notificationId = await Helpers.uploadAndDownloadFileProgressNotification( 'downloadFile', 0, channelId, Constants.NOTIFICATION_STATUS.START, `Downloading ${fileDetails.name} File` ); downloadFileMutation.mutate( { "files": [fileDetails._id], }, { "onSuccess": async (filesDetails) => { console.log('filesDetails', filesDetails); await Helpers.cancelNotification(notificationId); Helpers.uploadAndDownloadFileProgressNotification( 'downloadFile', 1, channelId, Constants.NOTIFICATION_STATUS.END, `${fileDetails.name} downloaded successfully`, 'click to view file' ); const listOfFileNamesWithBuffer = filesDetails.fileBuffers.map( (data) => ({ "name": data.file.originalname, "buffer": data.file.buffer.data, }) ); await Helpers.downloadAndStoreFile(listOfFileNamesWithBuffer); }, "onError": ({ "response": { "data": { "error" }, }, }) => { Helpers.toastMessage( get(error, 'description', Constants.COMMON_ERROR_MESSAGE), 'error' ); }, } ); }; export default downloadFile;
downloadFileMutation 代码
import { encryptedLockerAxiosClient } from '#/utils'; import { getFilesApiEndpoint } from '#/config/routes'; const getFile = (fileIds, authToken) => encryptedLockerAxiosClient .post( getFilesApiEndpoint, { ...fileIds, }, { "headers": { "Authorization": authToken, }, } ) .then((data) => ({ "fileBuffers": data.data, "status": data.status, })); const useGetFile = (authToken, progressCallback, options = {}) => { const mutation = useMutation((fileIds) => getFile(fileIds, authToken), { ...options, }); return mutation; }; export default useGetFile;
原因分析
该错误是因为React Native底层的V8引擎对单个对象的可枚举属性数量有上限(196607)。当服务器返回的Buffer数据过大时,data.file.buffer.data会被解析成一个长度超过该上限的Uint8Array,触发引擎的属性数量限制检查,导致RangeError。
解决方案
方案1:Base64编码传输(适合中小文件)
通过将Buffer转为Base64字符串传输,避免直接传递超大数组:
服务器端修改
将MongoDB中存储的Buffer转为Base64字符串后返回:
// 假设服务器端使用Node.js/Express app.post('/api/files', async (req, res) => { const files = await File.find({ _id: { $in: req.body.files } }); const responseData = files.map(file => ({ file: { originalname: file.originalname, buffer: file.buffer.toString('base64') // 转Base64 } })); res.json(responseData); });
客户端修改
将接收的Base64字符串转回Uint8Array:
// downloadFile中onSuccess部分修改 const listOfFileNamesWithBuffer = filesDetails.fileBuffers.map((data) => { // Base64转Uint8Array const base64Str = data.file.buffer; const binaryStr = atob(base64Str); const buffer = Uint8Array.from(binaryStr, c => c.charCodeAt(0)); return { name: data.file.originalname, buffer }; }); await Helpers.downloadAndStoreFile(listOfFileNamesWithBuffer);
方案2:流式下载到本地(适合大文件)
使用React Native文件系统库直接流式下载文件,避免将整个Buffer加载到内存:
依赖安装
npm install react-native-fs # 或 yarn add react-native-fs
客户端代码修改
import RNFS from 'react-native-fs'; import { Helpers } from '#/utils'; import { get } from 'lodash'; const downloadFile = async (fileDetails, authToken) => { const channelId = await Helpers.createChannelForProgressNotificationOfUploadingAndDownloadingFiles(); const notificationId = await Helpers.uploadAndDownloadFileProgressNotification( 'downloadFile', 0, channelId, Constants.NOTIFICATION_STATUS.START, `Downloading ${fileDetails.name} File` ); // 本地存储路径 const downloadPath = `${RNFS.DocumentDirectoryPath}/${fileDetails.name}`; const downloadOptions = { fromUrl: `${API_BASE_URL}/api/files/${fileDetails._id}`, // 改为直接返回二进制文件的接口 toFile: downloadPath, headers: { Authorization: authToken }, // 进度回调 progress: (res) => { const progress = res.bytesWritten / res.contentLength; Helpers.uploadAndDownloadFileProgressNotification( 'downloadFile', progress, channelId, Constants.NOTIFICATION_STATUS.IN_PROGRESS, `Downloading ${fileDetails.name} File`, `${Math.round(progress * 100)}%` ); }, progressDivider: 10 // 每10%更新一次通知 }; try { await RNFS.downloadFile(downloadOptions).promise; await Helpers.cancelNotification(notificationId); Helpers.uploadAndDownloadFileProgressNotification( 'downloadFile', 1, channelId, Constants.NOTIFICATION_STATUS.END, `${fileDetails.name} downloaded successfully`, 'click to view file' ); // 后续可记录文件路径到本地数据库 } catch (error) { Helpers.toastMessage( get(error, 'message', Constants.COMMON_ERROR_MESSAGE), 'error' ); } }; export default downloadFile;
总结
- 中小文件优先选择Base64编码传输,实现简单且兼容JSON接口;
- 大文件推荐流式下载到本地文件系统,避免内存溢出,提升性能。
内容的提问来源于stack exchange,提问作者Shail Patel
相关产品推荐
相关产品推荐

