Node.js:循环读取WebSocket更新的JSON文件报错,需实现错误重运行
解决Node.js读取频繁更新的JSON文件时的JSON解析错误
嘿,这个问题太常见了——你遇到的就是文件写入和读取的竞态条件:当你的循环在读取JSON文件时,WebSocket刚好正在往这个文件里写入数据,导致你读到的是还没写完的半拉JSON,自然JSON.parse就会抛出Unexpected end of JSON input错误。
下面给你几个实用的解决方案,按推荐程度排序:
1. 直接跳过文件中间层(最优解)
既然这些JSON数据是通过WebSocket推送过来的,为什么还要多此一举写到文件里再读呢?直接在WebSocket的消息回调里处理数据不香吗?这样完全绕开了文件读写的竞态问题,还能提升性能。
示例代码:
const WebSocket = require('ws'); const ws = new WebSocket('your-websocket-url'); ws.on('message', (data) => { try { // 直接解析WebSocket传来的原始数据 const jsonData = JSON.parse(data); // 这里处理你的业务逻辑,比如计算RSI之类的 checkPairs(jsonData); } catch (err) { console.error('解析WebSocket数据失败:', err); } }); // 原来的checkPairs可以改成直接接收数据,不用读文件 function checkPairs(pairData) { // 你的业务逻辑... console.log(`${pairData.symbol} rsi ${pairData.rsi}`); }
2. 原子化文件写入(如果必须用文件存储)
如果因为某些原因一定要把数据写到文件里,那就要让写入过程变成原子操作。具体做法是:先把数据写入一个临时文件,等写入完成后,再把临时文件重命名成目标文件。因为在大多数操作系统中,文件重命名操作是原子性的——要么旧文件存在,要么新文件完整存在,不会出现中间状态。
示例写入代码:
const fs = require('fs').promises; const path = require('path'); async function writeJsonAtomically(filePath, data) { const tmpFilePath = `${filePath}.tmp`; try { // 先写临时文件 await fs.writeFile(tmpFilePath, JSON.stringify(data), 'utf8'); // 原子重命名成目标文件 await fs.rename(tmpFilePath, filePath); } catch (err) { // 出错了要删掉临时文件,避免垃圾文件 await fs.unlink(tmpFilePath).catch(() => {}); throw err; } } // WebSocket回调里用这个函数写入 ws.on('message', async (data) => { try { const jsonData = JSON.parse(data); await writeJsonAtomically('./your-file.json', jsonData); } catch (err) { console.error('处理WebSocket数据失败:', err); } });
这样你的读取逻辑就不会读到半拉的JSON了,放心读就行。
3. 给读取操作加重试机制(快速修复方案)
如果暂时没法改写入逻辑,那就在读取文件的时候加个重试机制——当解析失败时,隔几十毫秒再读一次,重试个3-5次,大概率能拿到完整的文件。
示例带重试的读取函数:
const fs = require('fs').promises; async function readJsonWithRetry(filePath, maxRetries = 3, delayMs = 50) { let retries = 0; while (retries < maxRetries) { try { const fileContent = await fs.readFile(filePath, 'utf8'); return JSON.parse(fileContent); } catch (err) { retries++; if (retries >= maxRetries) throw err; // 等待一会儿再重试 await new Promise(resolve => setTimeout(resolve, delayMs)); } } } // 你的循环里用这个函数代替直接读文件 async function checkPairs() { try { const pairData = await readJsonWithRetry('./your-pair-file.json'); console.log(`${pairData.symbol} rsi ${pairData.rsi}`); } catch (err) { console.error('多次读取文件失败:', err); } }
4. 改用文件监听代替主动轮询
如果你的循环是定时轮询读文件,不如改成监听文件的变化事件,只有当文件确实更新完成后再去读取。推荐用chokidar库(比原生fs.watch更可靠):
首先安装依赖:
npm install chokidar
然后示例代码:
const chokidar = require('chokidar'); const fs = require('fs').promises; // 监听目标文件 const watcher = chokidar.watch('./your-pair-file.json', { persistent: true, ignoreInitial: false // 初始加载时也触发一次 }); watcher.on('add', readAndProcessFile); watcher.on('change', readAndProcessFile); async function readAndProcessFile(filePath) { try { const fileContent = await fs.readFile(filePath, 'utf8'); const pairData = JSON.parse(fileContent); console.log(`${pairData.symbol} rsi ${pairData.rsi}`); } catch (err) { console.error('处理文件失败:', err); } }
这样就不用自己写循环轮询了,文件更新时自动触发处理,还能避免读到不完整的内容(大多数情况下,文件变化事件会在写入完成后触发)。
内容的提问来源于stack exchange,提问作者tel90
相关产品推荐
相关产品推荐

