Cómo programar y ejecutar trabajos cron en Node.js

Cómo escribir trabajos cron utilizando sentencias crontab y programarlos con el node-cron paquete.

Primeros pasos

Para este tutorial, vamos a utilizar el marco JavaScript de pila completa de CheatCode, Joystick. Joystick reúne un marco de interfaz de usuario de front-end con un back-end de Node.js para crear aplicaciones.

Para comenzar, querremos instalar Joystick a través de NPM. Asegúrese de estar usando Node.js 16+ antes de instalar para garantizar la compatibilidad (lea este tutorial primero si necesita aprender a instalar Node.js o ejecutar varias versiones en su computadora):

Terminal

npm i -g @joystick.js/cli

Esto instalará Joystick globalmente en su computadora. Una vez instalado, vamos a crear un nuevo proyecto:

Terminal

joystick create app

Después de unos segundos, verá un mensaje de cierre de sesión en cd en su nuevo proyecto y ejecute joystick start . Antes de hacerlo, necesitamos instalar una dependencia:node-cron .

Terminal

npm i node-cron

Después de que esté instalado, continúe e inicie su servidor:

Terminal

cd app && joystick start

Después de esto, su aplicación debería estar ejecutándose y estamos listos para comenzar.

¿Qué es un trabajo cron?

Un trabajo cron o "trabajo cronológico" (tomado del nombre de la herramienta crontab original que inventó el concepto de trabajo cron) es una tarea automatizada que se ejecuta en un momento específico o en un intervalo específico. Por ejemplo, en el mundo físico, es posible que te despiertes todos los días y sigas una rutina como:

  1. Dúchate (6:00 a. m.)
  2. Cepille sus dientes (6:15 AM)
  3. Vístete (6:30 a. m.)
  4. Desayunar (6:40 a. m.)

Cada parte de esa rutina es un "trabajo". Todos los días, "completa" o "ejecuta" ese trabajo. Lo más probable es que hagas estas mismas cosas aproximadamente a la misma hora todos los días.

De manera similar, en una aplicación, es posible que tenga alguna tarea que deba realizarse todos los días o en un momento específico, por ejemplo:

  1. Envíe un correo electrónico con el tráfico del día anterior, todos los días a las 12:00 a. m.
  2. Cada tres horas, borre los datos temporales de una tabla/colección de la base de datos.
  3. Una vez por semana, obtenga la última lista de precios de la API de un proveedor.

Cada uno de estos son trabajos que deben realizarse en nuestra aplicación. Debido a que no queremos ejecutarlos manualmente (o tenemos que recordar ejecutarlos), podemos escribir un cron trabajo en nuestro código que lo hace automáticamente por nosotros.

Los trabajos cron se pueden programar de una de dos maneras:automáticamente cuando iniciamos nuestra aplicación o bajo demanda a través de una llamada de función.

Cableando un trabajo cron

Afortunadamente, los trabajos cron son de naturaleza simple. Constan de dos partes clave:

  1. Una declaración crontab que describe cuándo debe ejecutarse un trabajo.
  2. Una función para llamar cuando la hora actual coincida con la instrucción crontab.

Para comenzar, escribiremos una función que pueda ejecutar varios trabajos cron por nosotros y luego veremos cómo conectar cada trabajo individual:

/api/cron/index.js

export default () => {
  // We'll write our cron jobs here...
}

No hay mucho aquí, solo una simple función de flecha. Nuestro objetivo será definir nuestros trabajos cron dentro de esta función y luego llamar a esta función cuando se inicie nuestro servidor de aplicaciones. Esto es intencional porque queremos asegurarnos de que nuestra aplicación esté funcionando antes programamos cualquier trabajo cron (para evitar contratiempos y asegurarnos de que el código del que dependen nuestros trabajos esté disponible).

Muy rápido, veamos cómo vamos a llamar a esto en el inicio del servidor:

/index.servidor.js

import node from "@joystick.js/node";
import api from "./api";
import cron from './api/cron';

node.app({
  api,
  routes: {
    "/": (req, res) => { ... },
}).then(() => {
  cron();
});

En el index.server.js archivo aquí (creado para nosotros cuando ejecutamos joystick create arriba), hemos hecho un pequeño cambio.

Al final de la llamada a node.app() —la función que inicia nuestra aplicación en Joystick— hemos agregado un .then() llamar de vuelta. Estamos usando esto porque esperamos node.app() para devolvernos una Promesa de JavaScript. Aquí, .then() está diciendo "después de node.app() se ha ejecutado y resuelto, llame a esta función".

En este código, "esta función" es la función que estamos pasando a .then() . Esta función se llama inmediatamente después de node.app() resuelve (es decir, JavaScript Promise ha señalado que su trabajo está completo y nuestro código puede continuar).

En la parte superior de nuestro archivo, hemos importado nuestro cron() función que especificamos en /api/cron/index.js . Dentro de nuestro .then() devolución de llamada, llamamos a esta función para iniciar nuestros trabajos cron después de que se inicie el servidor.

/api/cron/index.js

import cron from 'node-cron';
import { EVERY_30_SECONDS, EVERY_MINUTE, EVERY_30_MINUTES, EVERY_HOUR } from './scheduleConstants';

export default () => {
  cron.schedule(EVERY_30_SECONDS, () => {
    // We'll do some work here...
  });

  cron.schedule(EVERY_MINUTE, () => {
    // We'll do some work here...
  });

  cron.schedule(EVERY_30_MINUTES, () => {
    // We'll do some work here...
  });

  cron.schedule(EVERY_HOUR, () => {
    // We'll do some work here...
  });
}

De vuelta en nuestro /api/cron/index.js file completamos un poco nuestra función. Ahora, arriba, podemos ver que hemos importado el cron objeto del node-cron paquete que instalamos anteriormente.

Abajo en nuestra función exportada, llamamos al cron.schedule() función que toma dos argumentos:

  1. La declaración crontab que define la programación para el trabajo cron.
  2. Una función para llamar cuando llega el tiempo especificado por el programa.

En la parte superior de nuestro archivo, podemos ver algunas variables con nombre que se importan de un archivo que necesitamos crear en el /api/cron carpeta:scheduleConstants.js .

/api/cron/scheduleConstants.js

// NOTE: These can be easily generated with https://crontabkit.com/crontab-expression-generator

export const EVERY_30_SECONDS = '*/30 * * * * *';
export const EVERY_MINUTE = '* * * * * ';
export const EVERY_30_MINUTES = '*/30 * * * *';
export const EVERY_HOUR = '0 0 * * * *';

Aquí, tenemos cuatro instrucciones crontab diferentes, cada una especificando un horario diferente. Para que todo sea más fácil de entender en nuestro código, en este archivo estamos asignando un nombre amigable para los humanos a cada declaración para que podamos interpretar rápidamente la programación en nuestro código.

Las declaraciones crontab tienen una sintaxis única que involucra asteriscos (o "estrellas", si lo prefiere) donde cada estrella representa una unidad de tiempo. En orden, de izquierda a derecha, las estrellas representan:

  1. Minuto
  2. Segundo
  3. Hora
  4. Día del mes
  5. Mes
  6. Día de la semana

Como vemos arriba, cada estrella se puede reemplazar con números y caracteres para especificar ciertos intervalos de tiempo. Este es un gran tema, por lo que si tiene curiosidad sobre el funcionamiento interno de crontab, se recomienda que lea esta guía.

/api/cron/index.js

import cron from 'node-cron';
import fs from 'fs';
import { EVERY_30_SECONDS, EVERY_MINUTE, EVERY_30_MINUTES, EVERY_HOUR } from './scheduleConstants';

const generateReport = (interval = '') => {
  if (!fs.existsSync('reports')) {
    fs.mkdirSync('reports');
  }

  const existingReports = fs.readdirSync('reports');
  const reportsOfType = existingReports?.filter((existingReport) => existingReport.includes(interval));
  fs.writeFileSync(`reports/${interval}_${new Date().toISOString()}.txt`, `Existing Reports: ${reportsOfType?.length}`);
};

export default () => {
  cron.schedule(EVERY_30_SECONDS, () => {
    generateReport('thirty-seconds');
  });

  cron.schedule(EVERY_MINUTE, () => {
    generateReport('minute');
  });

  cron.schedule(EVERY_30_MINUTES, () => {
    generateReport('thirty-minutes');
  });

  cron.schedule(EVERY_HOUR, () => {
    generateReport('hour');
  });
}

De vuelta en nuestro código, ahora estamos listos para usar nuestros trabajos cron. Como vimos antes, estamos importando nuestras declaraciones crontab con nombre de /api/cron/scheduleConstants.js y pasándolos como primer argumento a cron.schedule() .

Ahora, estamos listos para hacer un trabajo real... o al menos, algo falso trabajo.

Arriba de nuestra función exportada y justo debajo de nuestras importaciones, hemos agregado una función generateReport() para simular el trabajo de "generar un informe" en algún intervalo. Esa función toma un interval arbitrario nombre e intenta crear un archivo en el reports directorio de nuestra aplicación. El nombre de cada archivo toma la forma de <interval>_<timestamp>.txt donde <interval> es el interval nombre que pasamos al generateReport() función y <timestamp> es la cadena de fecha ISO-8601 que marca cuando se creó el archivo.

Para llegar allí, primero, nos aseguramos de que el reports El directorio realmente existe (requerido ya que obtendremos un error si intentamos escribir un archivo en una ubicación que no existe). Para hacer eso, arriba, hemos importado fs del fs paquete:un paquete central de Node.js que se utiliza para interactuar con el sistema de archivos.

De ese paquete, usamos fs.existsSync() para ver si el reports directorio existe. Si no , seguimos adelante y lo creamos.

Si lo hace existe, a continuación, leemos el contenido actual del directorio (una lista de matriz de todos los archivos dentro del directorio) como existingReports y luego tome esa lista y fíltrela por interval escribe usando JavaScript Array.filter función.

Con todo esto, intentamos escribir nuestro archivo usando el <interval>_<timestamp>.txt patrón que describimos anteriormente como el nombre del archivo, y establecer el contenido de ese archivo igual a una cadena que dice Existing Reports: <count> donde <count> es igual al número existente de informes de interval escriba en el momento de la generación (por ejemplo, para el primer informe es 0 , para el siguiente es 1 , etc.).

¡Eso es todo! Ahora, cuando iniciemos nuestro servidor, deberíamos ver nuestros trabajos cron ejecutándose y los informes apareciendo en el /reports directorio.

Terminando

En este tutorial, aprendimos a escribir y programar trabajos cron en Node.js usando el node-cron paquete. Aprendimos cómo organizar nuestro código de trabajo cron y asegurarnos de llamarlo después de que se inicie nuestra aplicación. También aprendimos cómo funcionan las instrucciones crontab y cómo escribir varios trabajos cron usando constantes escritas previamente que hacen que nuestras declaraciones crontab sean más fáciles de entender.