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

TypeScript环境下如何为JSON文件创建更新队列规避read/write错误

TypeScript 实现JSON文件读写队列解决方案

核心思路是通过单例异步任务队列串行化所有对同一份JSON文件的读写操作,彻底消除并发读写导致的竞态、半写入等错误。


完整实现代码

import * as fs from 'fs/promises'
import * as path from 'path'

class JsonDBQueue {
  // 全局单例映射,不同JSON文件对应独立队列互不干扰
  private static instanceMap: Map<string, JsonDBQueue> = new Map()
  // 任务链,初始为已完成的Promise
  private taskChain: Promise<void> = Promise.resolve()
  private readonly dbFilePath: string

  private constructor(filePath: string) {
    this.dbFilePath = path.resolve(filePath)
  }

  // 获取对应文件的唯一队列实例
  public static getInstance(filePath: string): JsonDBQueue {
    if (!JsonDBQueue.instanceMap.has(filePath)) {
      JsonDBQueue.instanceMap.set(filePath, new JsonDBQueue(filePath))
    }
    return JsonDBQueue.instanceMap.get(filePath)!
  }

  // 通用入队方法,所有读写操作都需要通过该方法加入执行队列
  private enqueue<T>(task: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      // 将当前任务追加到队列末尾,保证串行执行
      this.taskChain = this.taskChain
        .then(async () => {
          const result = await task()
          resolve(result)
        })
        .catch((err) => {
          reject(err)
        })
    })
  }

  // 读取JSON文件内容
  public read<T = unknown>(): Promise<T> {
    return this.enqueue(async () => {
      const rawContent = await fs.readFile(this.dbFilePath, 'utf-8')
      return JSON.parse(rawContent) as T
    })
  }

  // 写入内容到JSON文件,先写临时文件再重命名,避免进程崩溃损坏原文件
  public write<T>(data: T): Promise<void> {
    return this.enqueue(async () => {
      const content = JSON.stringify(data, null, 2)
      const tempFilePath = `${this.dbFilePath}.tmp`
      await fs.writeFile(tempFilePath, content, 'utf-8')
      await fs.rename(tempFilePath, this.dbFilePath)
    })
  }

  // 原子更新操作,将读-改-写封装为单个原子任务,避免中间插入其他操作
  public update<T>(updater: (oldData: T) => T | Promise<T>): Promise<T> {
    return this.enqueue(async () => {
      const oldData = await this.read<T>()
      const newData = await updater(oldData)
      await this.write(newData)
      return newData
    })
  }
}

使用示例

// 初始化目标JSON文件的队列实例
const userDB = JsonDBQueue.getInstance('./data/users.json')

// 1. 直接写入初始数据
await userDB.write({ users: [] })

// 2. 读取JSON数据
const dbData = await userDB.read<{ users: Array<{ id: number; name: string }> }>()

// 3. 原子更新数据,并发调用也不会出现冲突
const updateResult = await userDB.update<{ users: Array<{ id: number; name: string }> }>((oldData) => {
  oldData.users.push({ id: Date.now(), name: '测试用户' })
  return oldData
})

// 并发调用测试:所有任务会按调用顺序串行执行,不会出现读写错乱
Promise.all([
  userDB.update(old => { old.users.push({ id: 1, name: '用户A' }); return old }),
  userDB.update(old => { old.users.push({ id: 2, name: '用户B' }); return old }),
  userDB.read()
]).then(([res1, res2, finalData]) => {
  // finalData必然包含id为1和2的两个用户
  console.log(finalData.users.length) // 输出2
})

核心特性说明

  • 单例映射保证同一个JSON文件只会对应一个队列实例,避免多处初始化导致的并发问题
  • 写入逻辑做了异常兼容,即使写入中途进程崩溃,也不会损坏原有JSON文件
  • 所有操作自动串行执行,不需要手动加锁,适配Node.js单线程事件循环特性
  • update方法封装原子操作,避免读和写之间插入其他操作导致的数据不一致

内容的提问来源于stack exchange,提问作者Grupo RXCO

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 22:24:07