0
0
Lập trình
Hưng Nguyễn Xuân 1
Hưng Nguyễn Xuân 1xuanhungptithcm

Lập lịch tác vụ với TypeScript và thư viện node-schedule

Đăng vào 8 tháng trước

• 8 phút đọc

Chủ đề:

#webdev#typescript

Lập lịch tác vụ với TypeScript và thư viện node-schedule

Trong bài viết này, chúng ta sẽ tìm hiểu cách tạo một trình lập lịch tác vụ đơn giản bằng TypeScript và thư viện node-schedule. Đây là một công cụ hữu ích cho việc quản lý và chạy các tác vụ định kỳ trong ứng dụng Node.js của bạn.

Giới thiệu về node-schedule

Thư viện node-schedule cho phép bạn lập lịch các tác vụ với cú pháp tương tự như cron. Bạn có thể chỉ định lịch trình cho các tác vụ dưới dạng chuỗi cron hoặc ngày giờ cụ thể. Điều này rất thích hợp cho các tác vụ như gửi email tự động, làm sạch cơ sở dữ liệu, hoặc bất kỳ hành động nào cần được thực hiện định kỳ.

Cài đặt thư viện

Trước tiên, bạn cần cài đặt thư viện cần thiết. Mở terminal và chạy lệnh sau:

bash Copy
npm install node-schedule @types/node-schedule

Cấu trúc mã nguồn

Dưới đây là mã nguồn cho một trình lập lịch đơn giản:

typescript Copy
import * as schedule from 'node-schedule';
import { Job } from 'node-schedule';

interface TaskConfig {
  name: string;
  schedule: string | Date;
  task: () => Promise<void> | void;
  enabled?: boolean;
}

class TaskScheduler {
  private jobs: Map<string, Job> = new Map();

  /**
   * Lập lịch một tác vụ với cú pháp giống cron hoặc ngày cụ thể
   * @param config Cấu hình tác vụ
   * @returns Instance Job
   */
  scheduleTask(config: TaskConfig): Job | null {
    if (!config.enabled && config.enabled !== undefined) {
      console.log(`Tác vụ ${config.name} đã bị vô hiệu, bỏ qua...`);
      return null;
    }

    try {
      const job = schedule.scheduleJob(config.name, config.schedule, async () => {
        console.log(`[${new Date().toISOString()}] Thực hiện tác vụ: ${config.name}`);
        try {
          await config.task();
          console.log(`[${new Date().toISOString()}] Tác vụ đã hoàn thành: ${config.name}`);
        } catch (error) {
          console.error(`[${new Date().toISOString()}] Tác vụ thất bại: ${config.name}`, error);
        }
      });

      if (job) {
        this.jobs.set(config.name, job);
        console.log(`Tác vụ đã được lập lịch: ${config.name} - Lần chạy tiếp theo: ${job.nextInvocation()}`);
      }

      return job;
    } catch (error) {
      console.error(`Lập lịch tác vụ thất bại: ${config.name}`, error);
      return null;
    }
  }

  /**
   * Lập lịch nhiều tác vụ cùng một lúc
   * @param configs Mảng cấu hình tác vụ
   */
  scheduleTasks(configs: TaskConfig[]): void {
    configs.forEach(config => this.scheduleTask(config));
  }

  /**
   * Hủy một tác vụ cụ thể
   * @param taskName Tên tác vụ cần hủy
   */
  cancelTask(taskName: string): boolean {
    const job = this.jobs.get(taskName);
    if (job) {
      job.cancel();
      this.jobs.delete(taskName);
      console.log(`Tác vụ đã bị hủy: ${taskName}`);
      return true;
    }
    console.warn(`Không tìm thấy tác vụ: ${taskName}`);
    return false;
  }

  /**
   * Hủy tất cả các tác vụ đã lập lịch
   */
  cancelAllTasks(): void {
    this.jobs.forEach((job, name) => {
      job.cancel();
      console.log(`Tác vụ đã bị hủy: ${name}`);
    });
    this.jobs.clear();
  }

  /**
   * Lấy thông tin về tất cả các tác vụ đã lập lịch
   */
  getTasksInfo(): Array<{name: string, nextRun: Date | null}> {
    const tasksInfo: Array<{name: string, nextRun: Date | null}> = [];

    this.jobs.forEach((job, name) => {
      tasksInfo.push({
        name,
        nextRun: job.nextInvocation()
      });
    });

    return tasksInfo;
  }

  /**
   * Lập lịch lại một tác vụ đã tồn tại
   * @param taskName Tên tác vụ cần lập lịch lại
   * @param newSchedule Lịch trình mới (cron hoặc ngày)
   */
  rescheduleTask(taskName: string, newSchedule: string | Date): boolean {
    const job = this.jobs.get(taskName);
    if (job) {
      const success = job.reschedule(newSchedule);
      if (success) {
        console.log(`Tác vụ đã được lập lịch lại: ${taskName} - Lần chạy tiếp theo: ${job.nextInvocation()}`);
      }
      return success;
    }
    console.warn(`Không tìm thấy tác vụ: ${taskName}`);
    return false;
  }

  /**
   * Tắt máy một cách an toàn - hủy tất cả các tác vụ
   */
  shutdown(): void {
    console.log('Đang tắt trình lập lịch...');
    this.cancelAllTasks();
    schedule.gracefulShutdown();
  }
}

Tính năng chính của mã nguồn này:

  1. Kiểm tra kiểu: Hỗ trợ TypeScript hoàn toàn với các interface và kiểu dữ liệu chính xác.
  2. Lập lịch linh hoạt: Hỗ trợ nhiều định dạng lập lịch:
    • Cú pháp giống cron ('0 9 * * *' cho mỗi ngày lúc 9 giờ sáng)
    • Ngày cụ thể (new Date())
    • RecurrenceRule cho các mẫu phức tạp.
  3. Quản lý tác vụ:
    • Lập lịch một hoặc nhiều tác vụ.
    • Hủy tác vụ cụ thể hoặc tất cả.
    • Lập lịch lại các tác vụ đã tồn tại.
    • Kích hoạt/vô hiệu hóa các tác vụ.
    • Lấy thông tin về các tác vụ đã lập lịch.
  4. Xử lý lỗi: Có xử lý lỗi với logging chi tiết.
  5. Tắt máy an toàn: Dọn dẹp đúng cách khi quá trình dừng lại.

Tham khảo nhanh về định dạng cron

Copy
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ ngày trong tuần (0 - 7) (0 hoặc 7 là Chủ nhật)
│ │ │ │ └───── tháng (1 - 12)
│ │ │ └────────── ngày trong tháng (1 - 31)
│ │ └─────────────── giờ (0 - 23)
│ └──────────────────── phút (0 - 59)
└───────────────────────── giây (0 - 59, tùy chọn)

Các mẫu thông dụng:

  • '*/5 * * * *' - Mỗi 5 phút.
  • '0 */2 * * *' - Mỗi 2 giờ.
  • '0 9-17 * * 1-5' - Mỗi giờ từ 9 giờ sáng đến 5 giờ chiều, từ Thứ Hai đến Thứ Sáu.
  • '0 0 1 * *' - Ngày đầu tiên của mỗi tháng lúc giữa đêm.

Ví dụ sử dụng

Dưới đây là ví dụ về cách sử dụng lớp TaskScheduler:

typescript Copy
// Khởi tạo trình lập lịch
const scheduler = new TaskScheduler();

// Các tác vụ ví dụ
const exampleTasks: TaskConfig[] = [
  {
    name: 'daily-report',
    schedule: '0 9 * * *', // Mỗi ngày lúc 9:00 sáng
    task: async () => {
      console.log('Đang tạo báo cáo hàng ngày...');
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log('Báo cáo hàng ngày đã được tạo thành công');
    },
    enabled: true
  },
  {
    name: 'database-cleanup',
    schedule: '0 2 * * 0', // Mỗi Chủ nhật lúc 2:00 sáng
    task: async () => {
      console.log('Đang bắt đầu dọn dẹp cơ sở dữ liệu...');
      await new Promise(resolve => setTimeout(resolve, 2000));
      console.log('Dọn dẹp cơ sở dữ liệu đã hoàn tất');
    },
    enabled: true
  },
];

// Lập lịch tất cả các tác vụ
scheduler.scheduleTasks(exampleTasks);

// Hiển thị thông tin các tác vụ đã lập lịch
console.log('\n=== Các tác vụ đã lập lịch ===');
const tasksInfo = scheduler.getTasksInfo();
tasksInfo.forEach(task => {
  console.log(`${task.name}: Lần chạy tiếp theo lúc ${task.nextRun}`);
});

// Tắt máy an toàn khi quá trình kết thúc
process.on('SIGINT', () => {
  console.log('\nNhận tín hiệu SIGINT, đang tắt máy an toàn...');
  scheduler.shutdown();
  process.exit(0);
});

Lời kết

Trình lập lịch tác vụ này không chỉ giúp bạn quản lý các tác vụ định kỳ mà còn cho phép bạn xử lý các tác vụ bất đồng bộ một cách dễ dàng. Bạn có thể mở rộng mã nguồn này với các tính năng như lưu trữ tác vụ, logic thử lại, hoặc hệ thống thông báo. Hãy thử nghiệm và tùy chỉnh theo nhu cầu của bạn!

Câu hỏi thường gặp (FAQ)

  1. Làm thế nào để lập lịch một tác vụ chỉ chạy một lần?
    Bạn có thể truyền một đối tượng Date cụ thể cho thuộc tính schedule trong cấu hình tác vụ.

  2. Thư viện này có hỗ trợ xử lý bất đồng bộ không?
    Có, node-schedule hỗ trợ chạy các tác vụ bất đồng bộ mà không gặp vấn đề gì.

  3. Tôi có thể hủy tất cả các tác vụ đã lập lịch không?
    Có, bạn có thể gọi phương thức cancelAllTasks() để hủy tất cả các tác vụ đã lập lịch.

Hy vọng bài viết này sẽ giúp bạn nắm vững cách sử dụng node-schedule để lập lịch các tác vụ trong ứng dụng của mình. Nếu bạn có bất kỳ câu hỏi nào, hãy để lại ý kiến của bạn dưới bài viết này nhé!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào