Перейти к содержимому
Поиск
Разработка

node-cron: задания по расписанию в Node.js — мастерская и примеры

Планируем отчёты, обновления и проверки в Node.js. Разбираем время, наложения, ошибки, остановку и несколько экземпляров приложения в практической мастерской.

Александр Некрасов
node-cron: задания по расписанию в Node.js — часы, очередь запусков и журнал событий

Сервис должен обновить сводку утром, проверить просроченные заявки каждые пять минут, подготовить отчёт по понедельникам или убрать собственные временные данные ночью. Пользователь не должен каждый раз нажимать кнопку, а разработчику нужен понятный способ выразить расписание и увидеть результат выполнения.

Node-cron запускает функции Node.js в заданные моменты времени. Но полезная автоматизация начинается после первой строки schedule: нужно выбрать часовой пояс, избежать наложения, отличить ошибку от пропущенного запуска и корректно остановить приложение. Разберём эти задачи на актуальной версии 4.6.0, с мини-мастерской, запускаемыми исходниками и практическим размещением на Linux.

Что делает node-cron и где заканчивается его ответственность

Node-cron — библиотека планирования внутри процесса Node.js. Вы задаёте cron-выражение и функцию, а работающий процесс отслеживает подходящие моменты и вызывает её. Функция может сформировать отчёт, обновить кэш или создать задание в очереди. Сама библиотека не знает, какой бизнес-результат считается успешным и где его следует хранить.

Это важная граница для эксплуатации. Если процесс остановлен, выключен сервер или приложение не запущено после обновления, обычное расписание не продолжает жить самостоятельно. Нужен менеджер службы, наблюдение за состоянием и выбранный способ обработки пропущенного периода. Успешная регистрация задачи в памяти ещё не означает, что через неделю будет готов нужный отчёт.

В небольшом сервисе отдельный процесс планировщика может быть достаточно простым решением. В системе с длительными заданиями, несколькими серверами и требованиями к повторным попыткам планировщик обычно становится только источником событий. Он определяет, когда создать работу, а очередь и worker отвечают за выполнение. Разделение этих обязанностей помогает выбрать инструмент без лишних ожиданий.

Официальная документация: назначение и быстрый старт.

Практические задачи: отчёты, напоминания, кэш и интеграции

В административной панели удобно готовить утреннюю сводку до начала рабочего дня. Планировщик выбирает момент, функция собирает завершённый период и сохраняет результат, а пользователь открывает готовый отчёт. Если сборка занимает много времени, вместо неё планировщик отправляет короткое задание в очередь и не удерживает тяжёлую работу в своём процессе.

Другой сценарий — проверка сроков заявок. Периодическая функция читает записи, которым пора изменить статус или показать напоминание. Важно выбирать данные по фактическому состоянию и фиксировать выполненное действие: повторный запуск после сбоя не должен отправлять человеку одно и то же сообщение несколько раз. Расписание определяет частоту проверки, но не заменяет эту бизнес-логику.

Для обновления кэша полезно заранее вычислить популярную сводку или освежить данные интеграции. Здесь нужны ограничения внешнего запроса, понятная реакция на недоступность источника и сохранение последнего пригодного результата. Наконец, ночная очистка временных файлов требует строго выделенной папки и политики хранения. В учебной мастерской все данные синтетические: реальные письма, внешние API и пользовательские файлы не затрагиваются.

Cron, setInterval, системный таймер и очередь

SetInterval удобен для короткого повторения через относительный интервал внутри уже работающего процесса. Cron-выражение описывает календарные условия: например, каждый рабочий день в определённый час. Это разные задачи. «Каждые двадцать четыре часа после старта» и «каждый день в девять по местному времени» могут привести к разным моментам после перезапуска и сезонного перевода часов.

Системный cron или systemd timer запускает программу на уровне Linux. Такой вариант удобен для отдельной консольной команды, которая выполняет работу и завершается. Node-cron полезен, когда расписание связано с существующим Node.js-приложением, его функциями, событиями и конфигурацией. Не нужно автоматически запускать оба механизма для одной операции: так легко получить дублирование.

Очередь отвечает за другую часть процесса: хранение заданий, повторные попытки, конкурентность и состояние выполнения. Для долгой обработки планировщик может положить задание в BullMQ, а отдельный worker его выполнит. Выбор не обязательно сводится к одному инструменту. Часто наиболее понятная архитектура состоит из маленького планировщика и проверяемого обработчика с собственным жизненным циклом.

Мини-мастерская: изучаем расписание и жизненный цикл

В мастерской восемь задач: короткий запуск каждые пять секунд; сравнение часовых поясов и прогноз ежедневных 09:00; start, stop и ручной execute; долгая работа с noOverlap; ошибка второго вызова; предел трёх плановых запусков; случайная задержка и пропущенные слоты. Выбирайте сценарий, создавайте экземпляр и наблюдайте счётчики, состояние и журнал. В браузерной модели кнопка «+5 секунд» продвигает учебное время от фиксированной даты, позволяя пройти опыт без ожидания настоящего сервера.

У режима часовых поясов две разные задачи: короткий job выполняется каждые пять секунд, а ежедневное расписание 09:00 только прогнозируется. Настоящий Node.js использует getNextRuns для трёх будущих моментов. В опыте с noOverlap работа длится 4,2 секунды при интервале две секунды. Во втором вызове сценария ошибок специально возникает DEMO_FAILURE, после чего следующий слот остаётся обычным плановым запуском. Jitter добавляет до 1200 мс перед работой, а видимая задержка также зависит от планирования ОС.

Для опыта с пропусками настоящий проект создаёт отдельный Worker: он один раз блокирует собственный event loop на 2,2 секунды, сохраняя отзывчивость HTTP-сервера. Это контролируемая демонстрация, а не рекомендация использовать busy loop в приложении. На сайте модель показывает смысл этих событий; фактические события node-cron возникают только в запущенном Node.js-проекте.

На сайте работает явно обозначенная браузерная модель с виртуальным временем. Кнопка шага помогает быстро увидеть последствия настроек; она не запускает настоящий node-cron на WordPress. В скачиваемом проекте режим server подключает тот же интерфейс к реальному Node.js API. При ошибке API приложение показывает её и не подменяет выполнение моделью незаметно.

Проверяйте не только успешный пример. Сравните короткую работу с долгой защищённой задачей, ручной запуск и запуск по расписанию, успешный результат и специально подготовленную ошибку. Затем измените часовой пояс и посмотрите, как одна и та же запись соотносится с UTC. Именно такие различия помогают перенести библиотеку в реальный сервис без неожиданного поведения.

На небольшом экране мастерскую удобно открыть отдельно. Встроенный блок имеет собственную прокрутку.

Установка и версия: почему важно сверять актуальный API

В материале используется node-cron 4.6.0 и Node.js 24. Библиотека устанавливается через npm, а в готовом проекте зависимости закреплены lock-файлом. Для нового эксперимента подходит install с явной версией; для скачанного архива используйте npm ci, чтобы повторить проверенный состав пакетов.

В Интернете много примеров для старых веток. Не переносите параметры из них механически: в современной версии для задачи, которая должна начинаться остановленной, используется createTask. Запуск по требованию выполняется через execute. Кроме того, текущая библиотека уже содержит средства просмотра будущих моментов и распределённой координации, поэтому описание возможностей следует привязывать к проверенной версии.

Начните с маленького файла .mjs, который выводит собственное сообщение и не меняет рабочие данные. Убедитесь, что понимаете, когда создаётся расписание и когда вызывается функция. Затем переходите к конкретной операции приложения. Даже простая автоматизация должна иметь имя, журнал, ограничение длительности и понятный способ остановки.

npm install node-cron@4.6.0
node demo.mjs
import cron from 'node-cron';

const task = cron.createTask('*/5 * * * * *', async context => {
  console.log('Учебный запуск', context.date.toISOString());
}, { name: 'demo-tick', timezone: 'UTC', maxExecutions: 3 });

task.start(); // Три плановых запуска, затем задача уничтожится.

Официальная документация: проверенный релиз 4.6.0.

Пять или шесть полей: читаем cron-выражение без путаницы

Обычное выражение содержит минуты, часы, день месяца, месяц и день недели. В node-cron можно добавить шестое поле слева — секунды. Если поля секунд нет, запуск относится к нулевой секунде подходящей минуты. Поэтому пять звёздочек означают каждую минуту, а шесть — каждую секунду.

Звёздочка выбирает любые значения, список перечисляет нужные, диапазон задаёт промежуток, а шаг выбирает повторение внутри поля. Например, */5 в поле минут выбирает минуты, кратные пяти. Это не таймер «через пять минут после того, как пользователь сохранил настройку»: расписание привязано к календарной сетке.

Для интерфейса полезно показывать выражение вместе с человеческим пояснением и ближайшими запусками. Пользователь может случайно вставить лишнее поле или перепутать часы и минуты, а синтаксически правильная строка всё равно будет означать другую частоту. Не начинайте с самых сложных комбинаций: сначала проверьте несколько простых расписаний, затем добавляйте ограничения под конкретную задачу.

# Пять полей:
# минута час день-месяца месяц день-недели
*/5 * * * *        # каждые пять минут
0 9 * * 1-5       # в 09:00 по будням
0 3 1 * *         # в 03:00 первого числа

# Шесть полей, секунды слева:
*/10 * * * * *    # каждые десять секунд

Официальная документация: синтаксис, диапазоны и шаги.

Проверка выражения и просмотр ближайших запусков

Проверка синтаксиса помогает отклонить невозможную строку до создания задачи. В современной версии есть обычная validate и более подробные средства анализа. Но валидность не равна полезности: корректное выражение может запускать слишком тяжёлую работу каждую секунду или выбирать момент, когда внешний сервис недоступен.

Метод getNextRuns позволяет посмотреть несколько будущих моментов без ожидания в реальном времени. В административном интерфейсе это хороший этап перед сохранением: пользователь видит дату в выбранной зоне и соответствующий UTC. Для такой проверки можно создать остановленную задачу с пустой функцией, получить прогноз и сразу освободить её ресурсы.

Проект приложения должен отдельно ограничивать частоту и допустимые операции. Поле расписания не должно одновременно становиться полем команды, URL или произвольного JavaScript. Пользователь выбирает разрешённый тип задачи и её условия, а исполняемая функция определяется доверенным кодом. Такой интерфейс проще объяснить и значительно легче сопровождать.

const expression = '0 9 * * 1-5';
if (!cron.validate(expression)) throw new Error('Некорректное расписание');
const preview = cron.createTask(expression, () => {}, { timezone: 'Europe/Moscow' });
console.log(preview.getNextRuns(5).map(date => date.toISOString()));
preview.destroy();

Часовой пояс: девять утра где именно

Без явной настройки задача использует часовой пояс системы. На локальном компьютере и VPS он может различаться, поэтому выражение неожиданно начнёт срабатывать в другое время после переноса. Для бизнес-расписания лучше явно задавать IANA-зону, например Europe/Moscow, и отдельно показывать её в настройках задачи.

Храните абсолютные временные отметки журнала в понятном едином формате, обычно UTC, а при показе пользователю преобразуйте их в нужную зону. В событии полезно отличать запланированный момент от фактического времени начала. Это помогает заметить задержку и не перепутать её с ошибкой преобразования времени.

Не заменяйте региональную зону постоянным смещением без понимания требований. Если задача должна выполняться в одно и то же местное время, сезонные правила региона имеют значение. Если требуется стабильное абсолютное время без сезонных переходов, UTC часто проще. Решение принимается по смыслу операции: утреннее напоминание сотруднику и техническое обновление кэша могут иметь разные требования.

const task = cron.createTask('0 9 * * 1-5', async () => {
  // Здесь вызывается разрешённая функция приложения.
}, { name: 'morning-summary', timezone: 'Europe/Moscow' });

DST: пропавший час и повторяющееся местное время

В регионах с сезонным переводом часов некоторые местные моменты не существуют, а другие встречаются дважды. Это свойство календаря, которое нельзя исправить только красивой строкой cron. Если задача назначена на такой момент, нужно заранее решить, соответствует ли поведение планировщика требованиям бизнеса.

Документация текущей версии описывает пропуск несуществующего времени при весеннем переходе и выбор первого вхождения повторяющегося времени при осеннем. Для критичных операций проверьте конкретную зону, даты и версию библиотеки отдельными тестами. Не переносите ожидания из другой cron-библиотеки или системного планировщика без сверки.

Если ежедневная операция обязана состояться для каждого бизнес-дня, полезно хранить обработанный период и проверять пропуски независимо от часов. Например, задача периодически ищет ещё не подготовленные дневные сводки. Тогда расписание запускает проверку, а полноту работы определяет состояние данных. Это также помогает пережить простой сервера, когда не было даже возможности выполнить календарный callback.

Официальная документация: часовые пояса и сезонные переходы.

Schedule и createTask: запуск планировщика не равен вызову функции

Schedule создаёт задачу и сразу включает наблюдение за расписанием. Это не означает, что функция выполняется немедленно при объявлении: она ждёт подходящего момента. Для явного немедленного вызова предназначен execute. Это различие особенно заметно в ночной задаче, которую оператор впервые запускает днём и ожидает увидеть результат сразу.

CreateTask создаёт остановленную задачу. Такой вариант удобен, чтобы сначала подключить обработчики событий, проверить конфигурацию и только затем разрешить плановые вызовы. В примерах он делает жизненный цикл заметным: stopped, start, ожидание, выполнение и завершение. Для административной панели можно показать эти состояния отдельно от последнего результата самой работы.

Не смешивайте состояние расписания с состоянием бизнеса. Планировщик может быть включён и ожидать следующий момент, а последний экспорт при этом завершился ошибкой. Или расписание уже остановлено, но предыдущая inline-функция ещё заканчивает работу. Пользователь должен видеть достаточно информации, чтобы понять обе части ситуации, а не один неопределённый зелёный индикатор.

Async и await: задача должна сообщить о настоящем завершении

Для асинхронной работы callback должен вернуть promise, который завершается вместе с нужной операцией. Если запустить fetch или запись файла и сразу выйти из функции, планировщик может считать работу законченной раньше фактического результата. Это ломает длительность, защиту от наложения и сообщения об успехе.

Используйте async/await или возвращайте promise явно. Ошибка должна дойти до контролируемого обработчика, а не потеряться в необработанном отдельном действии. При этом HTTP-запросу, базе и внешней программе нужны собственные ограничения времени: ожидание promise само по себе не гарантирует, что работа когда-либо завершится.

Сначала подготовьте результат, проверьте его и только затем публикуйте. Например, отчёт записывается во временное место и становится доступным пользователю после успешного завершения. Если функция остановилась посередине, приложение должно отличать частичный артефакт от готового. Node-cron помогает выбрать момент выполнения, но эта последовательность остаётся частью вашей операции.

const task = cron.createTask('*/5 * * * *', async () => {
  const result = await buildReport();
  await verifyReport(result);
  await publishReport(result);
}, { name: 'report', noOverlap: true });
// buildReport/verifyReport/publishReport — функции вашего приложения.

NoOverlap: пропустить новый тик, пока предыдущая работа не закончилась

Если функция выполняется дольше интервала, следующий момент может наступить до её завершения. Без защиты получатся одновременно работающие копии. Для некоторых задач это допустимо, для других — приводит к гонке, лишним запросам или повторной отправке одного результата.

NoOverlap предотвращает наложение плановых исполнений этой задачи: занятый момент пропускается. Это не очередь ожидания. Пропущенный тик не превращается автоматически в отложенное задание, которое обязательно выполнится после освобождения. Если каждый период должен быть обработан, храните такие периоды отдельно и выбирайте подходящую архитектуру очереди или догоняющей обработки.

Также noOverlap не следует считать общей блокировкой всего кластера или универсальной защитой любого ручного вызова. Несколько процессов и кнопка execute имеют отдельные условия. В своём API ограничивайте одновременные запросы на ручной запуск и проверяйте идемпотентность операции. Важно, чтобы асинхронный callback действительно ожидал конец работы: иначе защита увидит искусственно короткое выполнение.

task.on('execution:overlap', context => {
  console.warn('Плановый запуск пропущен: предыдущая работа ещё идёт', {
    scheduledAt: context.date.toISOString()
  });
});

MaxExecutions и execute: ограниченный опыт и ручной запуск

MaxExecutions удобно использовать для короткого учебного опыта: задача выполнит заданное число плановых запусков и затем уничтожится. Это позволяет показать несколько событий без бесконечно работающего процесса. В обычном сервисе лимит может быть полезен для ограниченной серии операций, но состояние такой серии не стоит считать долговечным только потому, что оно находится в памяти.

Execute запускает функцию немедленно и возвращает promise результата. В версии 4.6.0 ручные вызовы не увеличивают счётчик maxExecutions. Это существенная деталь для кнопки «проверить сейчас»: оператор может выполнить тест, не расходуя плановые запуски. При переносе старого примера сверяйте точное поведение выбранной версии.

В нашем API ручная кнопка доступна только после паузы расписания и при отсутствии активной работы. Разрешены три ручных вызова на учебный экземпляр. Пауза не продлевает общее окно тридцать секунд: для нового полного опыта создайте задачу заново. Это ограничения стенда, а не универсальные правила execute. Ручная кнопка требует таких же прав и ограничений, как плановая задача. Она не должна обходить проверки входа, лимит частоты или защиту от повторной публикации. Покажите человеку, что именно будет выполнено, а после нажатия — реальный результат. Для ошибки полезно сохранить идентификатор запуска и предложить следующее действие, а не просто повторять запрос без конца.

Jitter: небольшая случайная задержка распределяет нагрузку

Много задач часто назначают на круглое время. В результате несколько процессов одновременно обращаются к базе или внешнему API, хотя работа могла бы начинаться в небольшом допустимом окне. MaxRandomDelay добавляет случайную задержку перед выполнением и помогает распределить такие старты.

Это не механизм повторной попытки и не способ исправить перегруженную архитектуру. Если суммарная работа превышает возможности системы, случайный сдвиг только изменит форму нагрузки. Если операция должна начаться точно в установленный момент, дополнительное окно может вообще не соответствовать требованиям. Настройку выбирают по смыслу задачи и наблюдаемым измерениям.

В журнале стоит сохранять запланированное и фактическое время, чтобы задержка была объяснимой. При сочетании с распределённой арендой блокировки окно jitter входит в период, на который требуется координация. В учебном опыте задержки малы и нужны для сравнения событий; они не являются рекомендацией для любого рабочего сервера.

Официальная документация: опции noOverlap, maxExecutions и maxRandomDelay.

Ошибки, повторы и идемпотентность

Неудача одного вызова не означает, что следующий плановый запуск восстановит все последствия. Функция могла успеть создать файл, отправить часть сообщений или изменить запись до ошибки. Если просто повторить её, появится дубликат. Поэтому надёжность начинается с определения операции, которую можно безопасно повторить.

Для отчёта таким ключом может стать тип отчёта и период. Для уведомления — событие и получатель. Приложение хранит этот ключ и состояние, а запись результата выполняет согласованно с правилами своей базы. Один флаг в памяти процесса не защитит от перезапуска или другого экземпляра сервиса.

Повторы следует ограничивать по числу и времени, различая временный сбой и неправильный вход. Node-cron не нужно превращать в самодельную очередь через бесконечные setTimeout внутри callback. Когда требуются задержки, backoff, история и восстановление после падения worker, используйте подходящий механизм очереди. Планировщик в такой схеме создаёт работу, а не берёт на себя все гарантии её доставки.

События и журнал: ожидали одно время, начали в другое

События позволяют увидеть начало, успешное завершение, ошибку, пропуск и наложение. Подключайте слушателей до старта задачи, если нужно зафиксировать весь её жизненный цикл. В структурированном журнале полезны имя задачи, идентификатор исполнения, запланированное время, фактическое начало, окончание и итог.

Не выводите в журнал всё окружение или полный объект задания по привычке. Там могут быть секреты, персональные данные и внутренние параметры. Для пользователя достаточно понятного статуса и идентификатора, по которому оператор найдёт очищенную техническую диагностику. Подробный текст ошибки должен попадать в контролируемое место, а не в публичную страницу.

Отдельно наблюдайте отсутствие событий. Если процесс не запустился, он может не написать ни одной ошибки. Внешняя проверка возраста последнего успешного результата обнаружит такой случай лучше, чем мониторинг только исключений. Для бизнес-задачи важен готовый отчёт или выполненная проверка, а не просто живой процесс с красивым health-ответом.

task.on('execution:finished', context => {
  console.log(JSON.stringify({
    task: task.name, execution: context.execution?.id,
    scheduledAt: context.date.toISOString(),
    finishedAt: context.execution?.finishedAt?.toISOString(),
    status: 'finished'
  }));
});
task.on('execution:failed', context => {
  console.error('Задача не выполнена', context.execution?.error?.message);
});

Официальная документация: события и контекст исполнения.

Stop, destroy и корректное завершение сервиса

Stop выключает дальнейшее планирование, а destroy окончательно удаляет задачу. Для inline-функции остановка расписания не прерывает уже выполняющийся JavaScript посередине. Поэтому кнопка «остановить» должна объяснять, запрещает ли она новые старты или действительно отменяет текущую операцию через предусмотренный механизм.

У фоновых задач в отдельном процессе поведение управления отличается: там существует дочерний процесс, который нужно завершать и наблюдать. В этой статье важно не смешивать две модели. Для текущей версии также доступен cron.shutdown, предназначенный для завершения планировщика при остановке приложения; его следует согласовать с жизненным циклом HTTP-сервера и выполняемых операций.

В учебном сервере explicit destroy, окончание сессии и завершение сервиса дополнительно отменяют собственные ожидающие promise через AbortController. Это возможность наших специально написанных операций, а не универсальное умение node-cron прервать любой код. В сценарии missed пауза завершает Worker; для повторения создаётся новый экземпляр.

Для управляемой остановки сервис сначала перестаёт принимать новые задания, затем ждёт допустимое время завершения текущей работы и закрывает ресурсы. Если превышен предел, необходимо честно зафиксировать незавершённое состояние и проверить его после старта. Не публикуйте частичный результат как успешный только ради быстрого выхода процесса. Практический порядок зависит от того, что именно выполняет функция.

Официальная документация: жизненный цикл и управление задачей.

Несколько экземпляров приложения: координация не возникает сама

Когда приложение запускается в нескольких процессах, каждый обычно получает тот же код расписания. Без дополнительного решения утренний отчёт может начать строиться несколько раз. Это встречается при масштабировании контейнеров, кластерном запуске и временном одновременном существовании старого и нового выпуска.

В актуальном node-cron есть distributed и координаторы. Простейший вариант с NODE_CRON_RUN выделяет один экземпляр, которому разрешены запуски. Остальные должны быть явно отключены от этой роли. Такое распределение обязанностей не даёт автоматической высокой доступности: если выбранный исполнитель не работает, другой не станет им сам только из-за одинакового кода.

Для координации через общее хранилище требуется корректный coordinator, согласованные часы и срок аренды, превышающий длительность работы вместе с задержкой. Даже это не отменяет идемпотентность и не создаёт транзакционное exactly-once для внешних действий. В нашем учебном проекте границы демонстрируемого режима указаны отдельно; полноценный отказоустойчивый кластер не следует подразумевать по одному параметру конфигурации.

cron.schedule('0 3 * * *', async () => {
  // Разрешённая операция приложения.
}, { name: 'nightly-summary', distributed: true });
// В этой схеме ровно один экземпляр получает NODE_CRON_RUN=true.
// Остальные — NODE_CRON_RUN=false. Это не автоматический failover.

Официальная документация: распределённая координация и её ограничения.

Когда подключать BullMQ и отдельный worker

Планировщик подходит для определения момента запуска. Если задача должна переживать перезапуск, иметь несколько попыток, ограниченную конкурентность и доступную историю, удобнее передать её очереди. Например, в девять утра создаётся задание на отчёт за вчера, а worker забирает его, проверяет вход и сохраняет результат.

Такой подход позволяет отдельно масштабировать обработку, не создавая столько же новых расписаний. Веб-приложение показывает состояние по идентификатору, планировщик остаётся небольшим, а тяжёлые операции выполняются в специально выделенной среде. При этом очередь тоже нужно обслуживать: сохранять нужные данные, ограничивать повторы и проверять восстановление после сбоя.

На сайте уже есть практический материал о BullMQ. Используйте его для части, связанной с заданиями, повторами и прогрессом. Не обещайте пользователю завершение только потому, что расписание добавило запись: в интерфейсе отдельно различаются ожидание, выполнение, готовый результат и ошибка.

Windows: первый запуск и повторение учебного опыта

Установите Node.js 24, распакуйте архив и откройте PowerShell в папке nodecron-project. Выполните npm ci, npm run build, npm test и npm start, затем откройте http://127.0.0.1:8135. Сервер по умолчанию слушает loopback. Команды относятся к полному проекту: сборка готовит локальные ресурсы, а интерфейс обращается к Node.js API. Для остановки используется Ctrl+C.

На локальном компьютере удобно проверить выражение, часовой пояс и обработчики событий. Но сон системы, закрытие терминала и перезапуск прерывают обычный процесс. Не считайте ноутбук надёжным круглосуточным исполнителем только потому, что код однажды сработал. Для постоянной задачи потребуется среда, которая должна оставаться включённой, и средство управления службой.

Сохраняйте исходники отдельно от рабочих секретов. Параметры среды задаются осознанно, а запись журнала не должна случайно раскрывать весь process.env. Для первых опытов используйте синтетические данные и короткое ограниченное расписание, чтобы после изучения не осталось бесконечно работающей задачи, о которой легко забыть.

npm ci
npm run build
npm test
npm start
# http://127.0.0.1:8135

Linux и контейнер: повторяемая среда выполнения

С установленным Node.js 24 последовательность остаётся той же: npm ci, npm run build, npm test и npm start. Если задаёте PORT через окружение, согласуйте его с PUBLIC_ORIGIN и адресом открытия интерфейса. Для Linux-проверки из Windows можно использовать подготовленный Docker-вариант; точные файлы и команды приведены в README. У рабочего процесса должна быть одна определённая роль планировщика, а не случайное число копий при каждом открытии терминала.

На сервере важно запускать тот же проверенный набор зависимостей из lock-файла и знать пользователя процесса. Пути к файлам, доступ к журналу и рабочий каталог не должны зависеть от случайной SSH-сессии администратора. Контейнер помогает закрепить среду, но не избавляет от настройки хранения результатов и конфигурации.

Не включайте одну задачу одновременно в системный cron и внутри постоянно работающего node-cron-приложения, если это не специально спроектированная схема. Проверьте число экземпляров и политику перезапуска. Резкое завершение контейнера не откатывает уже отправленный запрос или опубликованный файл; после рестарта приложению всё ещё нужна проверка незавершённых операций.

npm ci
npm run build
npm test
PORT=8135 PUBLIC_ORIGIN=http://127.0.0.1:8135 npm start

Linux VPS: служба, доступ, обновление и откат

В архиве есть nodecron.service и Caddyfile.example. Служба запускает один Node.js-процесс от отдельного пользователя nodecron, читает /etc/nodecron/runtime.env и работает из /srv/nodecron/current. Внешний origin задаётся явно; само приложение остаётся на loopback-порту 8135. Caddy принимает HTTPS и требует Basic Auth перед всем стендом, включая API. Домен cron.example.com заменяется вашим подготовленным адресом.

README проводит через передачу ZIP по SSH, распаковку отдельного выпуска, npm ci, сборку, тесты, права, установку службы и проверку журнала. У Node должен быть корректный абсолютный путь: интерактивный nvm-profile не является настройкой systemd. Сначала проверяют локальный health и настоящий учебный вызов, затем включают автозапуск. Для Caddy создают хеш пароля, каталог журнала, проверяют конфигурацию и только после этого выполняют reload, сохраняя другие сайты.

Разделите конфигурацию выпуска и секреты. Новый код размещается в отдельной папке, зависимости устанавливаются из lock, тесты выполняются до переключения. Если при обновлении старый и новый процесс могут работать одновременно, заранее определите, какой из них имеет право исполнять расписания. Иначе короткое окно развёртывания создаст дубликаты даже при корректной обычной работе.

Для отката сохраняйте предыдущий проверенный выпуск и совместимую конфигурацию. После запуска проверяйте не только health, но и один настоящий учебный вызов, журнал и ближайшее расписание. Рабочие данные задач и результаты резервируются по отдельной политике: архив исходников не возвращает удалённый отчёт или потерянную историю выполнения.

sudo systemctl daemon-reload
sudo systemctl start nodecron.service
sudo systemctl status nodecron.service --no-pager
curl -fsS http://127.0.0.1:8135/health
sudo journalctl -u nodecron.service -n 80 --no-pager
# После успешной проверки:
sudo systemctl enable nodecron.service

Границы учебного API: один сценарий вместо веб-терминала

Настоящий сервер принимает только заранее известные сценарии и действия. В запросе нет произвольного cron-выражения, JavaScript, shell-команды или адреса внешнего API. Часовой пояс выбирается из трёх вариантов. Такие ограничения позволяют повторять учебный опыт без выполнения неизвестной операции на сервере.

На сессию создаётся одна задача. В стенде ограничены число сессий, время жизни, частота запросов, ручные вызовы и длина журнала. Окно опыта составляет тридцать секунд, а кнопка создания нового экземпляра позволяет повторить его с чистым состоянием. Это ограничения мастерской, а не встроенные пределы библиотеки.

Для локального API предусмотрены проверка Host и Origin, сессионная cookie и CSRF-токен. На VPS доступ дополнительно закрывается перед всеми маршрутами через HTTPS-прокси. Такой стенд не является универсальной платформой запуска чужого кода. В собственном сервисе сохраняйте явный список операций, проверяйте права конкретного пользователя и отдельно рассчитывайте допустимую нагрузку.

Мониторинг: свежесть результата важнее зелёного процесса

Минимальный контроль включает дату последнего успешного результата, длительность, число ошибок, пропусков и наложений. Если отчёт должен быть готов к началу рабочего дня, мониторинг должен проверять именно это условие. Процесс может оставаться живым, а нужная функция — регулярно падать или не находить данные из-за неправильной конфигурации.

Наблюдайте задержку относительно запланированного момента и нагрузку event loop. Длительное синхронное вычисление в том же процессе мешает своевременному запуску других задач. Для тяжёлой работы используйте отдельный worker или подходящую изоляцию, а не просто увеличивайте допустимое опоздание до исчезновения предупреждений.

План реакции тоже должен быть понятным. Кто увидит ошибку, где найдёт журнал, как определит безопасный повтор и что произойдёт с пользователем, ожидающим результат? Для автоматизации полезнее небольшой набор таких проверяемых правил, чем большая панель с множеством счётчиков, за которыми не следует конкретное действие.

Исходники, выполненные проверки и дальнейшее развитие

В исходниках есть проверки настоящего node-cron: прогноз 09:00 для трёх зон, ручной вызов без расходования планового лимита, завершение текущей работы после паузы, noOverlap, ошибка, maxExecutions, jitter и пропуски в изолированном Worker. HTTP-набор проверяет сессии, Host, Origin, CSRF, границы входа, закрытые исходные файлы и согласованность параллельных действий. Модель проверяется отдельно: её события не выдаются за исполнение сервера.

Собственный код доступен под MIT, node-cron — под ISC, Bootstrap — под MIT. Архив включает интерфейс, настоящий Node.js-сервер, пресеты, модель, тесты, Docker, systemd и Caddy-шаблоны. Проверка этих коротких опытов не означает испытание круглосуточной production-нагрузки, распределённого кластера или физических мобильных устройств. Такие условия требуют отдельной проверки вашего приложения.

Развивайте пример от нужного результата: отчёт за период, проверка сроков, обновление определённого кэша. Сначала сделайте сам обработчик проверяемым без расписания. Затем добавьте планировщик, события и ограничения. Такой порядок облегчает диагностику: понятно, сломалось ли условие времени, входные данные или собственно операция.

Помните о двух уровнях гарантий. Библиотека помогает вызвать функцию по правилам времени и наблюдать её состояние. Приложение отвечает за права, идемпотентность, сохранение результата и восстановление после сбоев. Когда эти границы видны и в коде, и в интерфейсе, расписание становится полезной частью сервиса, а не скрытым таймером, о котором вспоминают только после ошибки.