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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:43:20