#DefaultScheduler
DefaultScheduler 是异步工厂,返回一个可直接使用的调度器实例。它把模型(默认 FSRS-7)与 dateChrono 以及标准调度策略组合在一起,因此你只需配置它的选项。内部它与自定义 defineScheduler 使用同一套「定义与实例」机制——预设只是把各部分装配起来的便捷方式。
#选项
从 FSRS-3~6 升级到 FSRS-7 时,省略 weights 以使用 FSRS-7 默认权重,或提供为 FSRS-7 训练的权重。重放历史用于重建卡片状态,不会转换旧版权重。
FSRS-7 卡片除 stability 和 difficulty 外还包含 stabilityFast。旧版 FSRS-3~6 的记忆状态需要通过 Reschedule 重放复习历史来重建;参数迁移不会迁移卡片状态,不应以 0.8 * stability 推测演化后的快速轨迹。预设为 FSRS-7 启用小数天数;enableShortTerm: false 仅关闭显式学习步骤,不会关闭模型的快速轨迹。
需要成本自适应保留率时,自行组合定义,用 schedulerCostAdrMiddleware 替换 schedulerDesiredRetentionMiddleware。Cost ADR 不是 DefaultScheduler 选项。
scheduler.nextInterval(memoryState, desiredRetention, { card, grade, elapsedDays }) 查询已计算的评分后状态所对应的间隔,只执行 handlers.nextInterval,不推进卡片或 chrono;共享间隔策略应放在这个钩子中。
| 选项 | 默认 | 用途 |
|---|---|---|
version | 'FSRS-7' | 选择 FSRS 模型:'FSRS-3'、'FSRS-4'、'FSRS-4.5'、'FSRS-5', 'FSRS-6' 或 'FSRS-7'。FSRS-7 无法迁移旧版 FSRS-3~6 的权重。 |
weights | 预设权重 | 可选的训练权重;省略时使用所选版本的预设默认值。FSRS-7 需要恰好 34 个 FSRS-7 权重;传入旧版权重会在构造调度器时抛出异常。 |
desiredRetention | 0.9 | 在计算间隔前应用的目标保留率。 |
enableShortTerm | true | 启用学习与再学习步骤。 |
learningSteps | ['1m', '10m'] | enableShortTerm 开启时应用的学习步骤。 |
relearningSteps | ['10m'] | enableShortTerm 开启时应用的再学习步骤。 |
graduatingInterval | 1 | 卡片处于 Review 时的最短间隔(天)。最终间隔更短时,卡片保持 Learning/Relearning。配置了步骤时,走完步骤以及 Review 卡片按 Hard、Good、Easy 都至少使用这个间隔。 |
allowModelOverride | FSRS-7: true; 其他版本: false | 模型间隔比当前步骤长时,改用模型间隔。Again 不超过 graduatingInterval;Hard、Good 的间隔达到 graduatingInterval 时提前毕业。 |
enableFuzz | false | 开启时分散间隔以增加变化。 |
maximumInterval | 36500 | 间隔的天数上限。 |
clearStatsOnForget | true | forget 是否清空 reps 与 lapses。 |
DefaultScheduler 始终使用 dateChrono,并按中间件页面所述的顺序组合标准策略:目标保留率、模糊间隔、统计、scheduled days、学习步骤、最大间隔、单调间隔。选择 version 只改变模型及其参数,不改变这套策略。
import { function DefaultScheduler<Version extends DefaultSchedulerVersion = "FSRS-7">(options?: DefaultSchedulerOptions<Version>): Promise<DefaultScheduler<Version>>
type DefaultScheduler<Version extends DefaultSchedulerVersion = "FSRS-7"> = (Version extends "FSRS-7" ? Omit<SchedulerCore<SchedulerCoreEnv<SchedulerEnvFor<FSRS7DefaultSchedulerModel, Chrono<{
readonly time: SRSSchema<{
input: Date;
output: Date;
}>;
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly fields: {
readonly card: SRSSchema<{
input: {
dueAt: Date;
lastReviewAt?: (Date | null) | undefined;
};
output: {
dueAt: Date;
lastReviewAt: Date | null;
};
}>;
readonly revlog: SRSSchema<{
input: {
dueAt: Date;
lastReviewAt?: (Date | null) | undefined;
reviewTime: Date;
};
output: {
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
};
}>;
};
}>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>, "definition"> : Omit<...> & {
...;
}) & {
...;
}
DefaultScheduler , type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating } from 'ts-fsrs'
const now = new Date('2026-01-01T00:00:00.000Z')
const const scheduler: DefaultScheduler<"FSRS-7"> scheduler = await DefaultScheduler<"FSRS-7">(options?: DefaultSchedulerOptions<"FSRS-7"> | undefined): Promise<DefaultScheduler<"FSRS-7">> DefaultScheduler ({
DefaultSchedulerOptions<"FSRS-7">.version?: "FSRS-7" | undefinedFSRS model and parameter migration version; defaults to FSRS-7.
version : 'FSRS-7',
DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.desiredRetention?: number | undefined desiredRetention : 0.9,
DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.enableShortTerm?: boolean | undefinedEnable explicit learning steps. FSRS-7 always retains its intrinsic fast trace.
enableShortTerm : true,
DefaultSchedulerOptions<"FSRS-7">.learningSteps?: readonly (`${number}d` | `${number}m` | `${number}h`)[] | undefinedDefaults to ['1m', '10m'].
learningSteps : ['1m', '10m'],
DefaultSchedulerOptions<"FSRS-7">.relearningSteps?: readonly (`${number}d` | `${number}m` | `${number}h`)[] | undefinedDefaults to ['10m'].
relearningSteps : ['10m'],
DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.enableFuzz?: boolean | undefined enableFuzz : false,
DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.maximumInterval?: number | undefined maximumInterval : 36_500,
})
const const card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
}
card = const scheduler: DefaultScheduler<"FSRS-7"> scheduler .newCard: (options?: ({
readonly cardId?: string | number | undefined;
} & {
readonly now?: Date | undefined;
}) | undefined) => {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
}
newCard ({ cardId: 'quick-start', now })
const const result: {
card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
};
revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
};
}
result = const scheduler: DefaultScheduler<"FSRS-7"> scheduler .review: (input: {
readonly card: Readonly<Omit<Readonly<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & {
readonly cardId: string | number;
readonly reps: number;
readonly lapses: number;
readonly scheduledDays: number;
readonly learningStep?: number | undefined;
} & SchedulerCoreFields$1<"review" | "new" | "learning">>, keyof SchedulerCoreFields$1<"review" | "new" | "learning">> & SchedulerCoreFields$1<"review" | "new" | "learning">>;
readonly grade: Grade;
readonly now?: Date | undefined;
}) => {
...;
}
review ({ card, grade: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, now })
console.log(
JSON.stringify(
{
state: const result: {
card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
};
revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
}
card .state,
dueAt: const result: {
card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
};
revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
}
card .dueAt,
stability: const result: {
card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
};
revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
}
card .stability,
stabilityFast: const result: {
card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
};
revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
}
card .stabilityFast,
difficulty: const result: {
card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
};
revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
}
card .difficulty,
rating: const result: {
card: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
lapses: number;
scheduledDays: number;
learningStep: number;
state: State;
scheduleStatus: "review" | "new" | "learning";
};
revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
scheduledDays: number;
learningStep: number;
reviewTime: Date;
state: State;
scheduleStatus: "review" | "new" | "learning";
rating: Grade;
}
revlog .rating,
},
null,
2
)
)
该工厂是异步的,使用返回的实例前要先 await DefaultScheduler(...)。示例把常用默认值写得很明确:version、desiredRetention、学习与再学习步骤、模糊间隔和 maximumInterval。省略 weights 即用所选 FSRS 版本的预设。
#预设还是自定义定义
当默认的 FSRS 模型、dateChrono 与标准策略集契合你的产品时,用 DefaultScheduler——这是最快且正确的起点。当需要不同的模型或 chrono、增减策略,或需要为每个用户校验配置时,用 defineScheduler。二者最终都落到同一套调度器实例 API(newCard、review、rollback、preview 与 forget)。定义与实例模型见调度器概览。