任务

Nitro tasks 允许在运行时执行一次性操作:例如数据库迁移、缓存清理或内容同步等命名的服务器端操作,你可以按需、按计划或从 CLI 运行这些操作

#选择加入实验性功能

Important

Tasks support is currently experimental. See nitrojs/nitro#1974 for the relevant discussion.

为了使用 tasks API,你需要启用实验性功能标志。Tasks 会从你的 serverDir 中扫描,同时也必须设置该配置:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  serverDir: "./server",
  experimental: {
    tasks: true
  }
})

本页面上的所有示例都假设使用此配置。

#定义任务

可以在 serverDir 内的 tasks/[name].ts 文件中定义任务。

Note

与路由和插件一样,只有在设置了 serverDir(或 scanDirs)时才会扫描任务;默认不会扫描任何目录

支持嵌套目录。任务名称将使用 : 连接。

server/
  tasks/
    cleanup.ts       <-- cleanup
    db/
      migrate.ts     <-- db:migrate
nitro.config.ts

示例:

server/tasks/db/migrate.ts
import { defineTask } from "nitro/task";

export default defineTask({
  meta: {
    name: "db:migrate",
    description: "Run database migrations",
  },
  run({ payload, context }) {
    console.log("Running DB migration task...");
    return { result: "Success" };
  },
});

#任务接口

defineTask 辅助函数接受一个具有以下属性的对象:

  • meta(可选):一个包含可选的 name 和 description 字符串字段的对象,用于在开发服务器和 CLI 中显示。
  • run(必需):一个接收 TaskEvent 并返回(或解析为)一个包含可选 result 属性的对象的函数。
interface Task<RT = unknown> {
  meta?: { name?: string; description?: string };
  run(event: TaskEvent): { result?: RT } | Promise<{ result?: RT }>;
}

#TaskEvent

run 函数接收一个具有以下属性的 TaskEvent 对象:

  • name:正在执行的任务的名称。
  • payload:一个包含传递给任务的任何数据的对象(Record<string, unknown>)。
  • context:一个 TaskContext 对象(根据运行时的不同,可能包含 waitUntil)。
interface TaskEvent {
  name: string;
  payload: TaskPayload;
  context: TaskContext;
}

#通过配置注册任务

除了基于文件的扫描之外,还可以直接在 Nitro 配置中注册任务。这对于向扫描到的任务添加描述,或用于由模块提供并指向自定义处理程序路径的任务非常有用。

nitro.config.ts
import { fileURLToPath } from "node:url";
import { defineConfig } from "nitro";

export default defineConfig({
  serverDir: "./server",
  experimental: {
    tasks: true
  },
  tasks: {
    // Describe a task scanned from `server/tasks/db/migrate.ts`
    "db:migrate": {
      description: "Run database migrations"
    },
    // Register a task from a custom location
    "db:seed": {
      handler: fileURLToPath(new URL("scripts/seed.ts", import.meta.url)),
      description: "Seed the database"
    }
  }
})

Warning

与路由处理程序不同,任务的 handler 会按原样导入,不会根据项目根目录解析,因此类似 "./scripts/seed.ts" 的路径会在构建时解析失败。请使用绝对路径(例如通过 fileURLToPath(new URL(..., import.meta.url))),或使用包名称

如果某个任务同时从 tasks/ 目录扫描到,并且在配置中定义,则配置中定义的 handler 优先级更高。

#定时任务

你可以使用 Nitro 配置定义定时任务,使其在指定的 cron 格式时间自动运行。

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  scheduledTasks: {
    // 每分钟运行 `cms:update` 任务
    '* * * * *': ['cms:update'],
    // 运行单个任务(字符串简写形式)
    '0 * * * *': 'db:cleanup'
  }
})

scheduledTasks 配置将 cron 表达式映射为单个任务名称(字符串)或任务名称数组。当多个任务被分配到同一个 cron 表达式时,它们会并行运行。

Tip

你可以使用 crontab.guru 轻松生成和理解 cron 表达式

当定时任务运行时,它会自动接收一个 payload,其中 scheduledTime 设置为当前时间戳(Date.now())。

Note

当你的部署目标支持时,优先使用 scheduledTasks(参见平台支持)。Nitro 会为你连接平台的原生调度器。如果你的平台不受支持,仍然可以通过从经过身份验证的 HTTP 端点调用 runTask,由外部调度器触发任务

#平台支持

  • dev、node_server、node_cluster、node_middleware、bun 和 deno_server 预设由 croner 引擎支持。
  • cloudflare_module 和 cloudflare_pages 预设原生集成了 Cron Triggers。Nitro 会在构建时自动在 wrangler 配置中生成 cron triggers,无需手动设置 wrangler
  • vercel 预设原生集成了 Vercel Cron Jobs。Nitro 会在构建时自动生成 cron job 配置,无需手动设置 vercel.json。你可以通过设置 CRON_SECRET 环境变量来保护 cron 端点
  • 计划支持更多预设(包括原生 primitives 支持)!

#waitUntil

在运行后台任务时,你可能需要确保服务器或工作进程等待任务完成。

根据运行时的不同,一个可选的 context.waitUntil 函数 可能 可用。

import { defineTask } from "nitro/task";

export default defineTask({
  run({ context }) {
    const promise = fetch(...)
    context.waitUntil?.(promise);
    await promise;
    return { result: "Success" };
  },
});

#以编程方式运行任务

要手动运行任务,你可以使用 nitro/task 中的 runTask(name, { payload?, context? }) 函数。

示例:

server/api/migrate.ts
import { defineHandler } from "nitro";
import { runTask } from "nitro/task";

export default defineHandler(async (event) => {
  // 重要:验证用户身份并验证 payload!
  const payload = Object.fromEntries(event.url.searchParams);
  const { result } = await runTask("db:migrate", { payload });

  return { result };
});

#错误处理

runTask 在以下情况下会抛出 HTTP 错误:

  • 任务不存在(状态 404)。
  • 任务没有处理程序实现(状态 501)。

在任务的 run 函数中抛出的任何错误都会传播给调用者。

#使用开发服务器运行任务

Nitro 的内置开发服务器公开了任务,以便无需编程即可轻松执行。

#使用 API 路由

#/_nitro/tasks

此端点返回可用任务名称及其元数据的列表。

// [GET] /_nitro/tasks
{
  "tasks": {
    "db:migrate": {
      "description": "Run database migrations"
    },
     "cms:update": {
      "description": "Update CMS content"
    }
  },
  "scheduledTasks": [
    {
      "cron": "* * * * *",
      "tasks": [
        "cms:update"
      ]
    }
  ]
}

#/_nitro/tasks/:name

此端点执行任务。你可以使用查询参数和 JSON 请求体来提供 payload。在 JSON 请求体中发送的 payload 必须位于 "payload" 属性下。

import { defineTask } from "nitro/task";

export default defineTask({
  meta: {
    name: "echo:payload",
    description: "Returns the provided payload",
  },
  run({ payload, context }) {
    console.log("Running echo task...");
    return { result: payload };
  },
});

Note

JSON 请求体中指定的键会覆盖查询参数中存在的任何值

#使用 CLI

Important

只有在 dev 服务器正在运行 时才能运行这些命令。你应该在第二个终端中运行它们

#列出任务

nitro task list

#运行任务

nitro task run db:migrate --payload "{}"

--payload 标志接受一个 JSON 字符串,该字符串将被解析并传递给任务。如果该值不是有效的 JSON 对象,任务将在没有 payload 的情况下运行。

Read more in Docs > Cli#nitro Task.

#注意事项

#并发

任务运行会按任务名称去重:每个任务在每个服务器实例中最多只能有一个正在运行的实例。对已在运行的任务调用 runTask 不会启动新的运行:所有并发调用者,即使传入不同的 payload,也会共享第一个正在运行的任务结果