队列
@adonisjs/queue 包目前处于实验阶段。在达到稳定版本之前,其 API 可能会在次要版本之间发生变化。请在你的 package.json 中锁定该包的版本,以避免更新时出现意外的破坏性变更。
本指南介绍 AdonisJS 中基于队列的后台任务处理。你将学习如何:
- 使用 Redis 或 Database 后端安装并配置队列系统
- 创建任务并在后台派发它们
- 通过派发时的去重防止重复任务
- 延迟任务、设置优先级并批量派发
- 配置带有指数、线性或固定退避的重试策略
- 使用 cron 表达式或时间间隔调度定时任务
- 启动 worker 以处理队列中的任务
- 使用 fake 适配器测试任务派发
概述
Web 应用经常需要执行那些在 HTTP 请求期间运行过慢或资源消耗过高的任务。发送邮件、生成报表、处理支付或调整图片尺寸,都是应该在后台完成的典型工作,以便用户能立即获得响应。
@adonisjs/queue 包为 AdonisJS 提供了任务队列系统,构建于
@boringnode/queue
之上。你将任务(job) 定义为带有类型化负载的类,从应用代码中派发它们,并运行一个独立的 worker 进程来领取并执行这些任务。
该包支持多种后端。Redis 适配器被推荐用于生产环境,提供了原子操作和高吞吐量。Database 适配器通过 Lucid 使用你现有的 SQL 数据库(PostgreSQL、MySQL 或 SQLite)。另外还提供了一个 Sync 适配器用于开发和测试,它会立即执行任务而无需单独的 worker。
安装
使用以下命令安装并配置该包:
node ace add @adonisjs/queue
查看 add 命令执行的步骤
-
使用检测到的包管理器安装
@adonisjs/queue包。 -
在
adonisrc.ts文件中注册以下服务提供者、命令和预加载文件。adonisrc.ts{ commands: [ // ...other commands () => import('@adonisjs/queue/commands') ], providers: [ // ...other providers () => import('@adonisjs/queue/queue_provider') ], preloads: [ // ...other preloads () => import('#start/scheduler') ] } -
创建
config/queue.ts文件。 -
创建用于定义定时任务的
start/scheduler.ts预加载文件。 -
定义
QUEUE_DRIVER环境变量及其校验。 -
如果选择了数据库驱动,则会创建一个迁移以建立队列表。
配置
配置文件位于 config/queue.ts。它定义了你的适配器、要使用的默认适配器、worker 设置,以及任务文件的存放位置。
另见: 配置桩文件
import env from '#start/env'
import { defineConfig, drivers } from '@adonisjs/queue'
export default defineConfig({
default: env.get('QUEUE_DRIVER', 'redis'),
adapters: {
redis: drivers.redis({
connectionName: 'main',
}),
sync: drivers.sync(),
},
worker: {
concurrency: 5,
idleDelay: '2s',
},
locations: ['./app/jobs/**/*.{ts,js}'],
})
default
派发任务时默认使用的适配器名称。该值通常通过 QUEUE_DRIVER 环境变量设置。
adapters
一组已命名的适配器。每个适配器都使用 drivers 辅助函数之一创建:drivers.redis()、drivers.database() 或 drivers.sync()。你可以配置多个适配器并在运行时切换。
worker
worker 进程的配置。有关所有可用选项,请参阅 Worker 配置 。
locations
指向你的任务文件的一组 glob 模式。队列系统使用这些模式自动发现并注册任务类。
{
locations: ['./app/jobs/**/*.{ts,js}'],
}
retry
应用于所有任务的全局重试配置,除非在队列或任务级别被覆盖。详见 重试与退避 。
queues
按队列进行的配置,允许你为特定队列设置不同的重试策略或默认任务选项。
{
queues: {
emails: {
retry: {
maxRetries: 5,
},
},
},
}
defaultJobOptions
应用于所有任务的默认选项。单个任务可以在其 static options 属性中覆盖这些选项。
::
适配器配置
Redis
Redis 适配器使用你的 @adonisjs/redis 连接。由于其原子操作与高吞吐量,它被推荐用于生产环境。
import { defineConfig, drivers } from '@adonisjs/queue'
export default defineConfig({
default: 'redis',
adapters: {
redis: drivers.redis({
// 使用 config/redis.ts 中的 'main' 连接
connectionName: 'main',
}),
},
// ...
})
要让该适配器工作,你必须已安装并配置了 @adonisjs/redis。
Database
Database 适配器通过 @adonisjs/lucid 使用 PostgreSQL、MySQL 或 SQLite 连接。当你想避免向基础设施中引入 Redis 时,这是一个不错的选择。
import { defineConfig, drivers } from '@adonisjs/queue'
export default defineConfig({
default: 'database',
adapters: {
database: drivers.database({
connectionName: 'primary',
}),
},
// ...
})
在选择数据库驱动进行安装时,会自动创建一个迁移。如果你需要手动创建这些表,请使用 QueueSchemaService:
import { BaseSchema } from '@adonisjs/lucid/schema'
import { QueueSchemaService } from '@adonisjs/queue'
export default class extends BaseSchema {
async up() {
const schemaService = new QueueSchemaService(this.db.getWriteClient())
await schemaService.createJobsTable()
await schemaService.createSchedulesTable()
}
async down() {
const schemaService = new QueueSchemaService(this.db.getWriteClient())
await schemaService.dropSchedulesTable()
await schemaService.dropJobsTable()
}
}
要让该适配器工作,你必须已安装并配置了 @adonisjs/lucid。
Sync
Sync 适配器会在同一进程内立即执行任务,无需单独的 worker。当你想立即看到任务结果时,这对开发和测试很有用。
import { defineConfig, drivers } from '@adonisjs/queue'
export default defineConfig({
default: 'sync',
adapters: {
sync: drivers.sync(),
},
// ...
})
你可以使用 QUEUE_DRIVER 环境变量按环境在适配器之间切换。在生产环境中使用 redis 或 database,在开发环境中使用 sync。
创建任务
任务(job) 是一个封装了要在后台执行的工作单元的类。每个任务都继承带有类型化负载的 Job 基类。
使用 make:job Ace 命令生成一个新的任务:
node ace make:job process_payment
这会在 app/jobs/process_payment.ts 创建一个任务类:
import { Job } from '@adonisjs/queue'
import type { JobOptions } from '@adonisjs/queue/types'
interface ProcessPaymentPayload {
orderId: number
amount: number
currency: string
}
export default class ProcessPayment extends Job<ProcessPaymentPayload> {
static options: JobOptions = {
queue: 'default',
maxRetries: 3,
}
async execute() {
const { orderId, amount, currency } = this.payload
// 使用你的支付网关处理支付
console.log(`Processing payment of ${amount} ${currency} for order ${orderId}`)
}
async failed(error: Error) {
console.error(`Payment processing failed for order ${this.payload.orderId}:`, error.message)
}
}
Job 类提供了两个需要实现的方法:
execute()(必需):包含主要的任务逻辑。此处抛出的任何错误都会触发重试机制。failed(error)(可选):当任务在耗尽所有重试后永久失败时调用。可用它来进行清理、记录日志或发送通知。
任务选项
通过设置 static options 属性来配置任务的默认行为。
queue
该任务被派发到的队列。默认为 'default'。
maxRetries
在任务永久失败之前的最大重试次数。除非在全局或队列级别配置,否则默认为 0。
priority
任务优先级,从 1 到 10。数字越小越先处理。默认为 5。
timeout
任务超时前的最大执行时间。接受毫秒为单位的数字,或像 '30s' 或 '5m' 这样的时长字符串。默认不超时。
failOnTimeout
是否在超时时将任务标记为永久失败。当为 false 时,超时的任务遵循正常的重试策略。默认为 false。
retry
特定于该任务的重试配置,覆盖全局和队列级别的配置。详见 重试与退避 。
removeOnComplete
控制已完成的任务是否保留在历史记录中。设为 true 可立即移除(默认),false 可永久保留,或设为带有 age 和/或 count 约束的对象。
{ removeOnComplete: { age: '7d', count: 1000 } }
removeOnFail
控制失败的任务是否保留在历史记录中。格式与 removeOnComplete 相同。
:::
任务上下文
在执行期间,你可以通过 this.context 访问当前任务的元数据:
export default class ProcessPayment extends Job<ProcessPaymentPayload> {
async execute() {
// 访问任务元数据
console.log(`Job ID: ${this.context.jobId}`)
console.log(`Attempt: ${this.context.attempt}`)
console.log(`Queue: ${this.context.queue}`)
console.log(`Priority: ${this.context.priority}`)
}
}
处理超时
对于长时间运行的任务,你可以检查 this.signal 以检测是否已达到超时,并优雅地处理:
import { Job } from '@adonisjs/queue'
import type { JobOptions } from '@adonisjs/queue/types'
interface GenerateReportPayload {
reportId: number
rows: number[]
}
export default class GenerateReport extends Job<GenerateReportPayload> {
static options: JobOptions = {
timeout: '5m',
failOnTimeout: false, // 重试而非永久失败
}
async execute() {
for (const rowId of this.payload.rows) {
// 在处理每一行之前检查任务是否已超时
if (this.signal?.aborted) {
throw new Error('Job timed out during report generation')
}
await this.processRow(rowId)
}
}
private async processRow(rowId: number) {
// 处理单个行
}
}
依赖注入
任务是通过 AdonisJS IoC 容器实例化的,因此你可以使用构造函数注入来访问服务:
import { inject } from '@adonisjs/core'
import { Job } from '@adonisjs/queue'
import type { JobOptions } from '@adonisjs/queue/types'
import PaymentService from '#services/payment_service'
interface ProcessPaymentPayload {
orderId: number
amount: number
currency: string
}
@inject()
export default class ProcessPayment extends Job<ProcessPaymentPayload> {
static options: JobOptions = {
queue: 'payments',
maxRetries: 3,
}
constructor(private paymentService: PaymentService) {
super()
}
async execute() {
await this.paymentService.charge(
this.payload.orderId,
this.payload.amount,
this.payload.currency
)
}
}
派发任务
使用静态的 dispatch 方法从应用中的任意位置派发任务。任务负载是类型安全的,与任务类上定义的泛型参数相匹配。
import ProcessPayment from '#jobs/process_payment'
export default class OrdersController {
async store({ request, response }: HttpContext) {
const order = await Order.create(request.body())
// 派发任务以进行后台处理
await ProcessPayment.dispatch({
orderId: order.id,
amount: order.total,
currency: 'USD',
})
return response.created(order)
}
}
从 Ace 命令派发任务
当你的应用为 HTTP 请求启动时,来自 locations 配置的任务文件会被自动加载。queue:work 命令也会在启动 worker 之前加载它们。
自定义的 Ace 命令则不同:在控制台启动生命周期中,任务不会被自动发现。如果你的命令会派发任务,并且你在本地或测试中使用 sync 适配器,请在派发前加载配置的任务位置。
import { BaseCommand } from '@adonisjs/core/ace'
import ImportOrders from '#jobs/import_orders'
export default class ImportOrdersCommand extends BaseCommand {
static commandName = 'orders:import'
static options = {
startApp: true,
}
async run() {
const queue = await this.app.container.make('queue.manager')
await queue.loadJobs()
await ImportOrders.dispatch({
source: 'warehouse',
})
}
}
如果你的命令只是将任务派发到 Redis 或 Database,导入任务类就足以将其入队。调用 queue.loadJobs() 使同一个命令与 sync 适配器保持兼容,后者会在当前进程中立即执行任务。
派发选项
dispatch 方法返回一个流畅的构建器,让你能够在将任务发送到队列之前自定义其行为:
// 派发到特定队列并设为高优先级
await ProcessPayment.dispatch({
orderId: order.id,
amount: order.total,
currency: 'USD',
})
.toQueue('payments')
.priority(1)
.toQueue(name)
将任务发送到特定队列,而非默认队列。
await ProcessPayment.dispatch(payload).toQueue('payments')
.priority(level)
设置任务优先级。数字越小越先处理(1 = 最高优先级,10 = 最低优先级)。
await ProcessPayment.dispatch(payload).priority(1)
.in(delay)
将任务执行延迟一个指定的时长。接受毫秒值或像 '5m'、'1h'、'7d' 这样的字符串。
// 24 小时后发送提醒
await SendReminder.dispatch(payload).in('24h')
.group(groupId)
分配一个组标识符以组织相关任务。便于跟踪批量操作。
await GenerateReport.dispatch(payload).group('monthly-reports-2025')
.with(adapter)
为该任务使用特定适配器,而非默认适配器。
await ProcessPayment.dispatch(payload).with('redis')
任务去重
任务去重 可防止多次单个任务派发将相同的工作加入队列。当 HTTP 重试、Webhook 重试或重复的按钮点击可能多次派发相同的后台任务时,这会很有用。
dedup 方法接受一个用户定义的 id。在底层,队列会为其加上任务名称前缀,因此两个不同的任务类可以使用相同的 id 而不会冲突。
import type { HttpContext } from '@adonisjs/core/http'
import Order from '#models/order'
import ProcessPayment from '#jobs/process_payment'
export default class OrdersController {
async retryPayment({ params, response }: HttpContext) {
const order = await Order.findOrFail(params.id)
const result = await ProcessPayment.dispatch({
orderId: order.id,
amount: order.total,
currency: order.currency,
})
.toQueue('payments')
.dedup({ id: `order:${order.id}`, ttl: '10m' })
return response.accepted({
jobId: result.jobId,
deduped: result.deduped,
})
}
}
当使用 .dedup() 时,派发结果会包含一个 deduped 结果值。当你的控制器或服务需要对被跳过或替换的派发做出不同反应时,可以使用该值。
| 结果 | 含义 |
|---|---|
added | 插入了一个新任务 |
skipped | 已存在重复的待处理任务,新的派发被忽略 |
replaced | 一个待处理或延迟的重复任务保留了其任务 id,但接收了最新的负载 |
extended | 一个重复任务被跳过,其去重窗口被刷新 |
去重支持以下派发模式:
| 方法调用 | 行为 |
|---|---|
.dedup({ id }) | 当具有相同键的现有任务仍然存在时,跳过重复项 |
.dedup({ id, ttl }) | 在一个时间窗口内跳过重复项 |
.dedup({ id, ttl, extend: true }) | 跳过重复项,并在每次重复时刷新时间窗口 |
.dedup({ id, ttl, replace: true }) | 替换现有待处理或延迟任务的负载 |
.dedup({ id, ttl, extend: true, replace: true }) | 通过替换负载并刷新时间窗口来防抖(debounce)派发 |
extend 和 replace 选项需要一个正的 ttl。使用 replace 时,只更新任务负载。现有任务会保留其队列、优先级、延迟和组 id。活跃的任务以及已保留的已完成或失败任务会被跳过,而非被修改。
去重仅由 Redis 和 Database 适配器针对单个任务派发应用。Sync 适配器会以内联方式运行每次派发,而批量派发或定时任务不会应用去重。
当防止重复很重要时,请使用 Redis 或 Database。对于批量或定时的流程,请在你的应用逻辑、数据库约束或专门的锁中强制唯一性。
用户提供的 id 长度必须不超过 400 个字符。最终构建为 <jobName>::<id> 的键长度必须不超过 510 个字符。
批量派发
当你需要派发许多相同类型的任务时,请使用 dispatchMany 以获得更好的性能。它在底层使用了批处理操作(Redis 流水线或 SQL 批量插入)。
import SendNewsletter from '#jobs/send_newsletter'
export default class NewsletterService {
async sendToAllSubscribers(subscribers: { email: string }[]) {
const payloads = subscribers.map((subscriber) => ({
email: subscriber.email,
subject: 'Monthly Newsletter',
}))
const { jobIds } = await SendNewsletter.dispatchMany(payloads)
.toQueue('emails')
.group('newsletter-march-2025')
console.log(`Dispatched ${jobIds.length} newsletter jobs`)
}
}
重试与退避
当任务抛出错误时,队列系统会根据配置的重试策略自动重试。你可以在三个级别配置重试,更具体的设置优先级更高:任务 > 队列 > 全局。
退避策略
退避策略控制重试尝试之间的延迟。该包提供了四种内置策略:
import { defineConfig, drivers } from '@adonisjs/queue'
import { exponentialBackoff } from '@adonisjs/queue'
export default defineConfig({
default: 'redis',
adapters: {
redis: drivers.redis({ connectionName: 'main' }),
},
retry: {
maxRetries: 3,
backoff: exponentialBackoff(),
},
// ...
})
指数退避(exponential backoff) 会使每次尝试的延迟翻倍:1s、2s、4s、8s,依此类推。这是大多数用例的推荐策略,因为它能防止压垮失败的服务。
import { exponentialBackoff } from '@adonisjs/queue'
// 默认:1s 基准、5m 最大、2x 倍数、启用抖动
exponentialBackoff()
// 自定义
exponentialBackoff({ baseDelay: '500ms', maxDelay: '1m' })
线性退避(linear backoff) 每次尝试增加基准量的延迟:5s、10s、15s、20s,依此类推。
import { linearBackoff } from '@adonisjs/queue'
// 默认:5s 基准、2m 最大
linearBackoff()
// 自定义
linearBackoff({ baseDelay: '10s', maxDelay: '5m' })
固定退避(fixed backoff) 每次重试都使用相同的延迟:10s、10s、10s,依此类推。
import { fixedBackoff } from '@adonisjs/queue'
// 默认:10s
fixedBackoff()
// 自定义
fixedBackoff('30s')
自定义退避(custom backoff) 让你完全控制策略配置:
import { customBackoff } from '@adonisjs/queue'
customBackoff({
strategy: 'exponential',
baseDelay: '100ms',
maxDelay: '30s',
multiplier: 3,
jitter: false,
})
在指数和线性策略上启用 jitter,以为重试延迟添加随机性。这能防止多个失败的任务在同一时刻重试,从而压垮下游服务(一种被称为「惊群效应」的模式)。
单任务重试
你可以为特定任务覆盖重试配置:
import { Job } from '@adonisjs/queue'
import { exponentialBackoff } from '@adonisjs/queue'
import type { JobOptions } from '@adonisjs/queue/types'
export default class ProcessPayment extends Job<ProcessPaymentPayload> {
static options: JobOptions = {
maxRetries: 5,
retry: {
backoff: exponentialBackoff({ baseDelay: '2s', maxDelay: '10m' }),
},
}
async execute() {
// 支付处理逻辑
}
}
定时任务
定时任务会在定义的时间间隔或 cron 表达式下自动运行。在 start/scheduler.ts 预加载文件中定义你的计划。
Cron 计划
使用 cron 表达式进行精确调度:
import CleanupExpiredSessions from '#jobs/cleanup_expired_sessions'
import GenerateWeeklyReport from '#jobs/generate_weekly_report'
// 每天午夜运行
await CleanupExpiredSessions.schedule({ retentionDays: 30 })
.cron('0 0 * * *')
.timezone('Europe/Paris')
// 每周一上午 9 点运行
await GenerateWeeklyReport.schedule({ type: 'weekly' })
.cron('0 9 * * MON')
.timezone('America/New_York')
间隔计划
对于更简单的用例,以固定的时间间隔调度任务:
import SyncInventory from '#jobs/sync_inventory'
// 每 5 分钟运行一次
await SyncInventory.schedule({ source: 'warehouse-api' })
.every('5m')
计划选项
计划构建器支持以下选项:
.cron(expression)
一个定义任务何时运行的 cron 表达式。与 .every() 互斥。
.every(interval)
两次运行之间的时间间隔。与 .cron() 互斥。
.id(scheduleId)
计划的自定义标识符。默认为任务类名。如果已存在具有相同 ID 的计划,它将被更新。
.timezone(tz)
用于计算 cron 表达式的 IANA 时区。默认为 'UTC'。
.from(date)
开始边界。在此日期之前不会派发任何任务。
.to(date)
结束边界。在此日期之后不会派发任何任务。
.between(from, to)
同时设置 .from() 和 .to() 的简写。
.limit(maxRuns)
计划运行的最大次数。 :::
管理计划
你可以使用 Schedule 类以编程方式管理计划:
import { Schedule } from '@adonisjs/queue'
export default class AdminController {
async listSchedules() {
// 列出所有计划
const schedules = await Schedule.list()
// 仅列出活跃的计划
const active = await Schedule.list({ status: 'active' })
return schedules
}
async pauseSchedule({ params }: HttpContext) {
const schedule = await Schedule.find(params.id)
if (schedule) {
await schedule.pause()
}
}
async resumeSchedule({ params }: HttpContext) {
const schedule = await Schedule.find(params.id)
if (schedule) {
await schedule.resume()
}
}
async triggerSchedule({ params }: HttpContext) {
const schedule = await Schedule.find(params.id)
if (schedule) {
// 立即派发定时任务
await schedule.trigger()
}
}
async deleteSchedule({ params }: HttpContext) {
const schedule = await Schedule.find(params.id)
if (schedule) {
await schedule.delete()
}
}
}
计划相关的 Ace 命令
该包提供了用于在终端中管理计划的 Ace 命令:
# 列出所有定时任务
node ace queue:scheduler:list
# 仅列出活跃的计划
node ace queue:scheduler:list --status=active
# 移除特定的计划
node ace queue:scheduler:remove <schedule-id>
# 移除所有计划
node ace queue:scheduler:clear
运行 worker
在启动 worker 之前,任务不会被处理。worker 是一个长期运行的进程,它会轮询队列中可用的任务并执行它们。
worker 还会为活跃的任务发送心跳。这些心跳会在处理器仍在运行时刷新任务所有权的时间戳,因此健康的长时间运行任务不会因为运行时间超过 stalledThreshold 就被当作卡住而回收。一个 worker 只能刷新它仍然拥有的任务,因此迟到的心跳无法延长一个已被另一个 worker 回收的任务。
使用 queue:work Ace 命令启动 worker:
node ace queue:work
Worker 选项
# 处理特定的队列
node ace queue:work --queue=payments,emails
# 设置并发数(同时处理的任务数量)
node ace queue:work --concurrency=10
你必须将 worker 作为一个独立的进程,与你的 Web 服务器一同启动。在 worker 运行之前,从你的应用派发的任务不会被处理。
在生产环境中,请使用像 PM2 这样的进程管理器或容器编排器来保持 worker 运行,并在其失败时重启。
Worker 配置
在你的 config/queue.ts 文件中配置 worker 的行为:
export default defineConfig({
// ...
worker: {
concurrency: 5,
idleDelay: '2s',
stalledThreshold: '30s',
stalledInterval: '30s',
maxStalledCount: 1,
gracefulShutdown: true,
},
})
concurrency
同时处理的最大任务数。默认为 1。
idleDelay
当没有可用任务时,worker 在下一次轮询之前等待的时间。默认为 '2s'。
timeout
任何任务的全局最大执行时间。可以通过 JobOptions.timeout 在任务级别覆盖。默认不超时。
stalledThreshold
一个任务在没有心跳的情况下,在被认定为卡住之前可以持续的时间。这是一个崩溃检测窗口,而非最大任务运行时长。当你想限制任务可运行的时长时,请使用 timeout 或 JobOptions.timeout。默认为 '30s'。
stalledInterval
worker 检查卡住任务的频率。默认为 '30s'。
maxStalledCount
一个卡住的任务在被永久标记失败之前,最多可被回收的次数。默认为 1。
gracefulShutdown
为 true 时,worker 会在收到 SIGINT/SIGTERM 信号后处理完正在运行的任务再停止。默认为 true。
:::
测试
该包提供了一个 fake 适配器,它会将派发的任务记录在内存中,并暴露断言辅助函数。这让你能够验证应用派发了正确的任务,而无需真正处理它们。
伪造队列
使用 QueueManager.fake() 将所有适配器替换为 fake 适配器,并使用 QueueManager.restore() 进行恢复:
import { test } from '@japa/runner'
import { QueueManager } from '@adonisjs/queue'
import ProcessPayment from '#jobs/process_payment'
test.group('Orders', (group) => {
group.each.teardown(() => {
QueueManager.restore()
})
test('dispatches a payment job when creating an order', async ({ client }) => {
const fake = QueueManager.fake()
const response = await client.post('/orders').json({
product_id: 1,
quantity: 2,
})
response.assertStatus(201)
// 断言任务已被派发
fake.assertPushed(ProcessPayment)
// 使用负载匹配进行断言
fake.assertPushed(ProcessPayment, {
payload: { orderId: 1, amount: 100, currency: 'USD' },
})
})
test('does not dispatch a payment job for free orders', async ({ client }) => {
const fake = QueueManager.fake()
await client.post('/orders').json({
product_id: 1,
quantity: 0,
})
fake.assertNotPushed(ProcessPayment)
})
})
断言方法
fake 适配器提供了以下断言方法:
assertPushed(job, query?)
断言某个任务已被派发。可按队列、负载或延迟进行筛选。
fake.assertPushed(ProcessPayment)
fake.assertPushed(ProcessPayment, { queue: 'payments' })
fake.assertPushed(ProcessPayment, {
payload: { orderId: 1 },
})
assertNotPushed(job, query?)
断言某个任务未被派发。
fake.assertNotPushed(SendEmail)
assertPushedCount(count, options?)
断言派发的任务总数,可按队列筛选。
fake.assertPushedCount(3)
fake.assertPushedCount(2, { queue: 'emails' })
assertNothingPushed()
断言根本没有派发任何任务。
fake.assertNothingPushed()
:::
进阶匹配
你可以使用函数进行更复杂的负载匹配:
import { test } from '@japa/runner'
import { QueueManager } from '@adonisjs/queue'
import SendNewsletter from '#jobs/send_newsletter'
test('dispatches newsletter jobs for all subscribers', async ({ client }) => {
const fake = QueueManager.fake()
await client.post('/newsletters/send')
// 使用函数进行匹配
fake.assertPushed(SendNewsletter, {
payload: (payload) => payload.email.endsWith('@example.com'),
})
// 检查延迟
fake.assertPushed(SendNewsletter, {
delay: (delay) => delay !== undefined && delay > 0,
})
})