React Native中限制下载x字节文件(不使用react-native-fetch-blob)
问题
需要在React Native中实现从URL下载文件的功能,且限制最多仅下载指定字节数(x字节)。原本可通过支持文件流的react-native-fetch-blob实现,但当前场景无法使用该库,希望找到替代方案(优先不依赖第三方库)。
在Web JavaScript中可通过以下代码实现:
async function download(url, maxBytes) { return new Promise(async (resolve, reject) => { try { const response = await fetch(url); if (!response.ok) { throw new Error('Network response was not ok'); } const reader = response.body.getReader(); let bytesRead = 0; let chunks = []; while (bytesRead < maxBytes) { const { done, value } = await reader.read(); if (done) { break; } chunks.push(value); bytesRead += value.length; } reader.cancel("Download size limit reached."); const result = new Uint8Array(bytesRead); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } let str = (new TextDecoder().decode(result)); // stuff to do with the str here return resolve(str); } catch (error) { console.error('Error downloading the file:', error); reject(); } }); }
但React Native的Fetch API不提供ReadableStream,导致上述代码无法运行,需要找到React Native中的可行实现方式。
解决方案
方案一:使用React Native内置XMLHttpRequest(无第三方依赖)
React Native的XMLHttpRequest支持arraybuffer类型的响应,且可通过监听readyState变化,在数据加载过程中控制下载字节数,达到上限时终止请求。
async function downloadWithLimit(url, maxBytes) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; let totalBytesRead = 0; let collectedChunks = []; let hasResolved = false; xhr.onreadystatechange = () => { if (hasResolved) return; // 当readyState为3时,响应正在加载,可读取已接收的数据 if (xhr.readyState === XMLHttpRequest.LOADING) { try { const currentChunk = new Uint8Array(xhr.response); const chunkLength = currentChunk.length; // 计算剩余可接收字节数 const remainingBytes = maxBytes - totalBytesRead; if (totalBytesRead + chunkLength >= maxBytes) { // 截取不超过上限的部分 const limitedChunk = currentChunk.slice(0, remainingBytes); collectedChunks.push(limitedChunk); totalBytesRead += remainingBytes; // 终止请求 xhr.abort(); // 合并所有chunk并解码 const resultBuffer = new Uint8Array(totalBytesRead); let offset = 0; collectedChunks.forEach(chunk => { resultBuffer.set(chunk, offset); offset += chunk.length; }); const decodedStr = new TextDecoder().decode(resultBuffer); hasResolved = true; resolve(decodedStr); } else { collectedChunks.push(currentChunk); totalBytesRead += chunkLength; } } catch (err) { reject(err); } } }; xhr.onload = () => { if (hasResolved) return; if (xhr.status >= 200 && xhr.status < 300) { // 下载完成且未超过限制,直接处理全部响应 const fullBuffer = new Uint8Array(xhr.response); const decodedStr = new TextDecoder().decode(fullBuffer); resolve(decodedStr); } else { reject(new Error(`请求失败,状态码:${xhr.status}`)); } }; xhr.onerror = () => { if (!hasResolved) { reject(new Error('网络请求出错')); } }; xhr.send(); }); }
说明:
- 利用
XMLHttpRequest.LOADING状态(readyState=3)实时读取已接收的二进制数据 - 累计字节数达到上限时,截取剩余允许的字节后终止请求
- 合并所有已接收的chunk并通过
TextDecoder解码为字符串
方案二:使用react-native-fs(第三方库备选)
如果内置方案存在兼容性问题,可使用轻量第三方库react-native-fs实现,它支持下载进度监听和中途终止下载。
步骤1:安装依赖
npm install react-native-fs --save # React Native 0.60+版本自动完成链接,无需额外操作
实现代码
import RNFS from 'react-native-fs'; async function downloadWithRNFS(url, maxBytes) { const tempFilePath = `${RNFS.TemporaryDirectoryPath}/temp_download_file`; let downloadJobId = null; let totalBytesWritten = 0; try { const downloadPromise = RNFS.downloadFile({ fromUrl: url, toFile: tempFilePath, progress: (progressData) => { totalBytesWritten = progressData.bytesWritten; // 达到字节上限时停止下载 if (totalBytesWritten >= maxBytes && downloadJobId) { RNFS.stopDownload(downloadJobId); } }, progressDivider: 1, // 每次接收数据都触发进度回调 }); downloadJobId = downloadPromise.jobId; await downloadPromise.promise; // 读取已下载的文件,截取指定字节数 const base64Data = await RNFS.read(tempFilePath, maxBytes, 0, 'base64'); // 将base64转为Uint8Array const uint8Array = Uint8Array.from(atob(base64Data), char => char.charCodeAt(0)); const decodedStr = new TextDecoder().decode(uint8Array); // 删除临时文件 await RNFS.unlink(tempFilePath); return decodedStr; } catch (error) { // 处理主动停止下载的情况 if (error.message.includes('stopDownload') && totalBytesWritten > 0) { const base64Data = await RNFS.read(tempFilePath, maxBytes, 0, 'base64'); const uint8Array = Uint8Array.from(atob(base64Data), char => char.charCodeAt(0)); const decodedStr = new TextDecoder().decode(uint8Array); await RNFS.unlink(tempFilePath); return decodedStr; } // 删除临时文件后抛出错误 await RNFS.unlink(tempFilePath).catch(() => {}); throw new Error(`下载失败:${error.message}`); } }
说明:
- 通过临时文件存储下载内容,达到字节上限时调用
stopDownload终止任务 - 读取临时文件的前
maxBytes字节,转换为字符串后清理临时文件
内容的提问来源于stack exchange,提问作者fres
相关产品推荐
相关产品推荐

