框架
版本
Debouncer API 参考
Throttler API 参考
速率限制器 API 参考
队列 API 参考
批处理器 API 参考
批处理器示例

队列指南

速率限制节流防抖(当操作过于频繁时会丢弃执行)不同,队列器可以配置为确保每个操作都得到处理。它们提供了一种管理和控制操作流程的方法,而不会丢失任何请求。这使得它们非常适合数据丢失不可接受的场景。队列还可以设置为具有最大大小,这对于防止内存泄漏或其他问题非常有用。本指南将介绍 TanStack Pacer 的队列概念。

队列概念

队列确保每个操作最终都会得到处理,即使它们进入的速度快于处理速度。与其他丢弃多余操作的执行控制技术不同,队列将操作缓冲在有序列表中,并根据特定规则对其进行处理。这使得队列成为 TanStack Pacer 中唯一的“无损”执行控制技术,除非指定了 maxSize,当缓冲区已满时,这可能导致项目被拒绝。

队列可视化

text
队列 (每 2 个滴答处理一个项目)
时间轴: [每个滴答 1 秒]
调用:        ⬇️  ⬇️  ⬇️     ⬇️  ⬇️     ⬇️  ⬇️  ⬇️
队列:       [ABC]   [BC]    [BCDE]    [DE]    [E]    []
已执行:     ✅     ✅       ✅        ✅      ✅     ✅
             [=================================================================]
             ^ 与速率限制/节流/防抖不同,
               所有调用最终都会按顺序处理

             [项目排队]   [稳定处理]   [清空]
              繁忙时         逐个处理         队列
队列 (每 2 个滴答处理一个项目)
时间轴: [每个滴答 1 秒]
调用:        ⬇️  ⬇️  ⬇️     ⬇️  ⬇️     ⬇️  ⬇️  ⬇️
队列:       [ABC]   [BC]    [BCDE]    [DE]    [E]    []
已执行:     ✅     ✅       ✅        ✅      ✅     ✅
             [=================================================================]
             ^ 与速率限制/节流/防抖不同,
               所有调用最终都会按顺序处理

             [项目排队]   [稳定处理]   [清空]
              繁忙时         逐个处理         队列

何时使用队列

当你需要确保每个操作都得到处理时,队列尤其重要,即使这意味着会引入一些延迟。这使其非常适合数据一致性和完整性比立即执行更重要的场景。当使用 maxSize 时,它还可以充当缓冲区,以防止过多的待处理操作使系统不堪重负。

何时不使用队列

在以下情况下,队列可能不是最佳选择:

  • 立即反馈比处理每个操作更重要
  • 你只关心最新的值(改用防抖
  • 你想将操作组合在一起(改用批处理

Tip

如果你目前正在使用速率限制、节流或防抖,但发现丢弃的操作导致了问题,那么队列很可能是你需要的解决方案。

TanStack Pacer 中的队列

TanStack Pacer 通过简单的 queue 函数和更强大的 Queuer 类提供队列功能。虽然其他执行控制技术通常倾向于其基于函数的 API,但队列通常受益于基于类的 API 提供的额外控制。

queue 的基本用法

queue 函数提供了一种创建始终运行的队列��简单方法,该队列在项目添加时处理它们:

ts
import { queue } from '@tanstack/pacer'

// 创建一个每秒处理项目的队列
const processItems = queue<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    wait: 1000,
    maxSize: 10, // 可选:限制队列大小以防止内存或时间问题
    onItemsChange: (queuer) => {
      console.log('当前队列:', queuer.peekAllItems())
    }
  }
)

// 添加要处理的项目
processItems(1) // 立即处理
processItems(2) // 1 秒后处理
processItems(3) // 2 秒后处理
import { queue } from '@tanstack/pacer'

// 创建一个每秒处理项目的队列
const processItems = queue<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    wait: 1000,
    maxSize: 10, // 可选:限制队列大小以防止内存或时间问题
    onItemsChange: (queuer) => {
      console.log('当前队列:', queuer.peekAllItems())
    }
  }
)

// 添加要处理的项目
processItems(1) // 立即处理
processItems(2) // 1 秒后处理
processItems(3) // 2 秒后处理

虽然 queue 函数易于使用,但它仅通过 addItem 方法提供了一个基本的始终运行的队列。对于大多数用例,你需要 Queuer 类提供的额外控制和功能。

Queuer 类的高级用法

Queuer 类提供了对队列行为和处理的完全控制:

ts
import { Queuer } from '@tanstack/pacer'

// 创建一个每秒处理项目的队列
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    wait: 1000, // 处理项目之间等待 1 秒
    maxSize: 5, // 可选:限制队列大小以防止内存或时间问题
    onItemsChange: (queuer) => {
      console.log('当前队列:', queuer.peekAllItems())
    }
  }
)

// 开始处理
queue.start()

// 添加要处理的项目
queue.addItem(1)
queue.addItem(2)
queue.addItem(3)

// 项目将逐个处理,每个项目之间有 1 秒的延迟
// 输出:
// 正在处理:1 (立即)
// 正在处理:2 (1 秒后)
// 正在处理:3 (2 秒后)
import { Queuer } from '@tanstack/pacer'

// 创建一个每秒处理项目的队列
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    wait: 1000, // 处理项目之间等待 1 秒
    maxSize: 5, // 可选:限制队列大小以防止内存或时间问题
    onItemsChange: (queuer) => {
      console.log('当前队列:', queuer.peekAllItems())
    }
  }
)

// 开始处理
queue.start()

// 添加要处理的项目
queue.addItem(1)
queue.addItem(2)
queue.addItem(3)

// 项目将逐个处理,每个项目之间有 1 秒的延迟
// 输出:
// 正在处理:1 (立即)
// 正在处理:2 (1 秒后)
// 正在处理:3 (2 秒后)

队列类型和排序

TanStack Pacer Queuer 的独特之处在于它能够通过其基于位置的 API 适应不同的用例。同一个 Queuer 可以表现得像传统队列、堆栈或双端队列,所有这些都通过相同的一致接口实现。

FIFO 队列(先进先出)

默认行为,项目按添加顺序处理。这是最常见的队列类型,遵循先添加的项目应首先处理的原则。使用 maxSize 时,如果队列已满,新项目将被拒绝。

ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    addItemsTo: 'back', // 默认
    getItemsFrom: 'front', // 默认
  }
)
queue.addItem(1) // [1]
queue.addItem(2) // [1, 2]
// 处理顺序:1,然后是 2
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    addItemsTo: 'back', // 默认
    getItemsFrom: 'front', // 默认
  }
)
queue.addItem(1) // [1]
queue.addItem(2) // [1, 2]
// 处理顺序:1,然后是 2

LIFO 堆栈(后进先出)

通过为添加和检索项目都指定“back”作为位置,队列器的行为类似于堆栈。在堆栈中,最近添加的项目是第一个被处理的项目。使用 maxSize 时,如果堆栈已满,新项目将被拒绝。

ts
const stack = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    addItemsTo: 'back', // 默认
    getItemsFrom: 'back', // 覆盖默认值以实现堆栈行为
  }
)
stack.addItem(1) // [1]
stack.addItem(2) // [1, 2]
// 项目将按顺序处理:2,然后是 1

stack.getNextItem('back') // 从队列后面而不是前面获取下一个项目
const stack = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    addItemsTo: 'back', // 默认
    getItemsFrom: 'back', // 覆盖默认值以实现堆栈行为
  }
)
stack.addItem(1) // [1]
stack.addItem(2) // [1, 2]
// 项目将按顺序处理:2,然后是 1

stack.getNextItem('back') // 从队列后面而不是前面获取下一个项目

优先级队列

优先级队列通过允许根据项目的优先级而不是仅仅根据其插入顺序对项目进行排序,从而为队列排序增加了另一个维度。每个项目都被分配一个优先级值,队列会自动按优先级顺序维护项目。使用 maxSize 时,如果队列已满,优先级较低的项目可能会被拒绝。

ts
const priorityQueue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    getPriority: (n) => n // 数字越大优先级越高
  }
)
priorityQueue.addItem(1) // [1]
priorityQueue.addItem(3) // [3, 1]
priorityQueue.addItem(2) // [3, 2, 1]
// 处理顺序:3、2,然后是 1
const priorityQueue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    getPriority: (n) => n // 数字越大优先级越高
  }
)
priorityQueue.addItem(1) // [1]
priorityQueue.addItem(3) // [3, 1]
priorityQueue.addItem(2) // [3, 2, 1]
// 处理顺序:3、2,然后是 1

启动和停止

Queuer 类通过 start()stop() 方法支持启动和停止处理。默认情况下,队列会自动开始处理。你可以设置 started: false 以使队列最初暂停,从而允许你执行以下任一操作:

  1. 稍后使用 start() 开始处理
  2. 通过以事件驱动的方式调用 getNextItem() 来手动处理项目
ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    started: false // 开始时暂停
  }
)

queue.start() // 开始处理项目
queue.stop()  // 暂停处理

// 在队列停止时手动处理项目(按自己的方式运行)
queue.getNextItem() // 获取下一个项目
queue.getNextItem() // 获取下一个项目
queue.getNextItem() // 获取下一个项目
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    started: false // 开始时暂停
  }
)

queue.start() // 开始处理项目
queue.stop()  // 暂停处理

// 在队列停止时手动处理项目(按自己的方式运行)
queue.getNextItem() // 获取下一个项目
queue.getNextItem() // 获取下一个项目
queue.getNextItem() // 获取下一个项目

附加功能

Queuer 提供了几种有用的队列管理方法:

ts
// 队列检查
queue.peekNextItem()           // 查看下一个项目而不删除它
queue.getSize()           // 获取当前队列大小
queue.getIsEmpty()        // 检查队列是否为空
queue.getIsFull()         // 检查队列是否已达到 maxSize
queue.peekAllItems()       // 获取所有排队项目的副本

// 队列操作
queue.clear()             // 删除所有项目
queue.reset()             // 重置为初始状态
queue.getExecutionCount() // 获取已处理项目的数量

// 事件处理(使用 onItemsChange 选项,而不是方法)
// 示例:
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    onItemsChange: (queuer) => {
      console.log('已处理:', queuer.peekAllItems())
    }
  }
)
// 队列检查
queue.peekNextItem()           // 查看下一个项目而不删除它
queue.getSize()           // 获取当前队列大小
queue.getIsEmpty()        // 检查队列是否为空
queue.getIsFull()         // 检查队列是否已达到 maxSize
queue.peekAllItems()       // 获取所有排队项目的副本

// 队列操作
queue.clear()             // 删除所有项目
queue.reset()             // 重置为初始状态
queue.getExecutionCount() // 获取已处理项目的数量

// 事件处理(使用 onItemsChange 选项,而不是方法)
// 示例:
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    onItemsChange: (queuer) => {
      console.log('已处理:', queuer.peekAllItems())
    }
  }
)

项目过期

Queuer 支持自动过期已在队列中停留时间过长的项目。这对于防止处理过时数据或对排队操作实施超时非常有用。

ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    expirationDuration: 5000, // 项目在 5 秒后过期
    onExpire: (item, queuer) => {
      console.log('项目已过期:', item)
    }
  }
)

// 或者使用自定义过期检查
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    getIsExpired: (item, addedAt) => {
      // 自定义过期逻辑
      return Date.now() - addedAt > 5000
    },
    onExpire: (item, queuer) => {
      console.log('项目已过期:', item)
    }
  }
)

// 检查过期统计信息
console.log(queue.getExpirationCount()) // 已过期的项目数
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    expirationDuration: 5000, // 项目在 5 秒后过期
    onExpire: (item, queuer) => {
      console.log('项目已过期:', item)
    }
  }
)

// 或者使用自定义过期检查
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    getIsExpired: (item, addedAt) => {
      // 自定义过期逻辑
      return Date.now() - addedAt > 5000
    },
    onExpire: (item, queuer) => {
      console.log('项目已过期:', item)
    }
  }
)

// 检查过期统计信息
console.log(queue.getExpirationCount()) // 已过期的项目数

过期功能对于以下情况特别有用:

  • 防止处理过时数据
  • 对排队操作实施超时
  • 通过自动删除旧项目来管理内存使用情况
  • 处理仅在有限时间内有效的临时���据

拒绝处理

当队列达到其最大大小(由 maxSize 选项设置)时,新项目将被拒绝。Queuer 提供了处理和监控这些拒绝的方法:

ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    maxSize: 2, // 队列中只允许 2 个项目
    onReject: (item, queuer) => {
      console.log('队列已满。项目已拒绝:', item)
    }
  }
)

queue.addItem(1) // 已接受
queue.addItem(2) // 已接受
queue.addItem(3) // 已拒绝,触发 onReject 回调

console.log(queue.getRejectionCount()) // 1
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    maxSize: 2, // 队列中只允许 2 个项目
    onReject: (item, queuer) => {
      console.log('队列已满。项目已拒绝:', item)
    }
  }
)

queue.addItem(1) // 已接受
queue.addItem(2) // 已接受
queue.addItem(3) // 已拒绝,触发 onReject 回调

console.log(queue.getRejectionCount()) // 1

初始项目

创建队列时,可以使用初始项目预先填充队列:

ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    initialItems: [1, 2, 3],
    started: true // 立即开始处理
  }
)

// 队列以 [1, 2, 3] 开始并开始处理
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    initialItems: [1, 2, 3],
    started: true // 立即开始处理
  }
)

// 队列以 [1, 2, 3] 开始并开始处理

动态配置

创建后可以使用 setOptions() 修改 Queuer 的选项,并使用 getOptions() 检索它们。此外,有几个选项通过回调函数支持动态值:

ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    wait: 1000,
    started: false
  }
)

// 更改配置
queue.setOptions({
  wait: 500, // 处理项目的速度提高一倍
  started: true // 开始处理
})

// 获取当前配置
const options = queue.getOptions()
console.log(options.wait) // 500
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    wait: 1000,
    started: false
  }
)

// 更改配置
queue.setOptions({
  wait: 500, // 处理项目的速度提高一倍
  started: true // 开始处理
})

// 获取当前配置
const options = queue.getOptions()
console.log(options.wait) // 500

动态选项

Queuer 中的几个选项通过接收队列器实例的回调函数支持动态值:

ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    // 基于队列大小的动态等待时间
    wait: (queuer) => {
      return queuer.getSize() > 10 ? 2000 : 1000
    }
  }
)
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  },
  {
    // 基于队列大小的动态等待时间
    wait: (queuer) => {
      return queuer.getSize() > 10 ? 2000 : 1000
    }
  }
)

以下选项支持动态值:

  • wait:可以是数字或返回数字的函数

这允许实现适应运行时条件的复杂队列行为。

性能监控

Queuer 提供了监控其性能的方法:

ts
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  }
)

// 添加并处理一些项目
queue.addItem(1)
queue.addItem(2)
queue.addItem(3)

console.log(queue.getExecutionCount()) // 已处理的项目数
console.log(queue.getRejectionCount()) // 已拒绝的项目数
const queue = new Queuer<number>(
  (item) => {
    // 处理每个项目
    console.log('正在处理:', item)
  }
)

// 添加并处理一些项目
queue.addItem(1)
queue.addItem(2)
queue.addItem(3)

console.log(queue.getExecutionCount()) // 已处理的项目数
console.log(queue.getRejectionCount()) // 已拒绝的项目数

异步队列

有关使用多个工作线程处理异步操作的信息,请参阅异步队列指南,其中介绍了 AsyncQueuer 类。

框架适配器

每个框架适配器都围绕队列器类构建了方便的钩子和函数。诸如 useQueueruseQueuedStateuseQueuedValue 之类的钩子是小型包装器,可以减少某些常见用例中你自己代码所需的样板代码。