V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
Lcode01
V2EX  ›  程序员

做定时任务,一定要用这个神库!

  •  1
     
  •   Lcode01 ·
    yaolifeng0629 · 3 天前 · 856 次点击
    • Hey, 我是 沉浸式趣谈
    • 本文首发于 [沉浸式趣谈] ,我的个人博客 https://yaolifeng.com 也同步更新。
    • 转载请在文章开头注明出处和版权信息。
    • 如果本文对您有所帮助,请 点赞评论转发,支持一下,谢谢!

    说实话,作为前端开发者,我们经常需要处理一些定时任务,比如轮询接口、定时刷新数据、自动登出等功能。

    过去我总是用 setTimeoutsetInterval,但这些方案在复杂场景下并不够灵活。

    我寻找了更可靠的方案,最终发现了 cron 这个 npm 包,为我的前端项目(特别是 Node.js 环境下运行的那部分)带来了专业级的定时任务能力。

    cron 包:不只是个定时器

    安装超级简单:

    npm install cron
    

    基础用法也很直观:

    import { CronJob } from 'cron';
    
    const job = new CronJob(
        '0 */30 * * * *', // 每 30 分钟执行一次
        function () {
            console.log('刷新用户数据...');
            // 这里放刷新数据的代码
        },
        null, // 完成时的回调
        true, // 是否立即启动
        'Asia/Shanghai' // 时区
    );
    

    看起来挺简单的,对吧?

    但这个小包却能解决前端很多定时任务的痛点。

    理解 cron 表达式,这个"魔法公式"

    刚开始接触 cron 表达式时,我觉得这简直像某种加密代码。* * * * * * 这六个星号到底代表什么?

    在 npm 的 cron 包中,表达式有六个位置(比传统的 cron 多一个),分别是:

    秒 分 时 日 月 周
    

    比如 0 0 9 * * 1 表示每周一早上 9 点整执行。

    我找到一个特别好用的网站 crontab.guru 来验证表达式。

    不过注意,那个网站是 5 位的表达式,少了"秒"这个位置,所以用的时候需要自己在前面加上秒的设置。

    月份和星期几还可以用名称来表示,更直观:

    // 每周一、三、五的下午 5 点执行
    const job = new CronJob('0 0 17 * * mon,wed,fri', function () {
        console.log('工作日提醒');
    });
    

    前端开发中的实用场景

    作为前端开发者,我在这些场景中发现 cron 特别有用:

    1. 在 Next.js/Nuxt.js 等同构应用中刷新数据缓存

    // 每小时刷新一次产品数据缓存
    const cacheRefreshJob = new CronJob(
        '0 0 * * * *',
        async function () {
            try {
                const newData = await fetchProductData();
                updateProductCache(newData);
                console.log('产品数据缓存已更新');
            } catch (error) {
                console.error('刷新缓存失败:', error);
            }
        },
        null,
        true,
        'Asia/Shanghai'
    );
    

    2. Electron 应用中的定时任务

    // 在 Electron 应用中每 5 分钟同步一次本地数据到云端
    const syncJob = new CronJob(
        '0 */5 * * * *',
        async function () {
            if (navigator.onLine) {
                // 检查网络连接
                try {
                    await syncDataToCloud();
                    sendNotification('数据已同步');
                } catch (err) {
                    console.error('同步失败:', err);
                }
            }
        },
        null,
        true
    );
    

    3. 定时检查用户会话状态

    // 每分钟检查一次用户活动状态,30 分钟无活动自动登出
    const sessionCheckJob = new CronJob(
        '0 * * * * *',
        function () {
            const lastActivity = getLastUserActivity();
            const now = new Date().getTime();
    
            if (now - lastActivity > 30 * 60 * 1000) {
                console.log('用户 30 分钟无活动,执行自动登出');
                logoutUser();
            }
        },
        null,
        true
    );
    

    踩过的那些坑

    使用 cron 包时我踩过几个坑,分享给大家:

    1. 时区问题:有次我设置了一个定时提醒功能,但总是提前 8 小时触发。一查才发现是因为没设置时区。所以国内用户一定要设置 'Asia/Shanghai'
    // 这样才会在中国时区的下午 6 点执行
    const job = new CronJob('0 0 18 * * *', myFunction, null, true, 'Asia/Shanghai');
    
    1. this 指向问题:如果你用箭头函数作为回调,会发现无法访问 CronJob 实例的 this 。
    // 错误示范
    const job = new CronJob('* * * * * *', () => {
        console.log('执行任务');
        this.stop(); // 这里的 this 不是 job 实例,会报错!
    });
    
    // 正确做法
    const job = new CronJob('* * * * * *', function () {
        console.log('执行任务');
        this.stop(); // 这样才能正确访问 job 实例
    });
    
    1. v3 版本变化:如果你从 v2 升级到 v3 ,要注意月份索引从 0-11 变成了 1-12 。

    实战案例:构建一个智能通知系统

    这是我在一个电商前端项目中实现的一个功能,用 cron 来管理各种用户通知:

    import { CronJob } from 'cron';
    import { getUser, getUserPreferences } from './api/user';
    import { sendNotification } from './utils/notification';
    
    class NotificationManager {
        constructor() {
            this.jobs = [];
            this.initialize();
        }
    
        initialize() {
            // 新品上架提醒 - 每天早上 9 点
            this.jobs.push(
                new CronJob(
                    '0 0 9 * * *',
                    async () => {
                        if (!this.shouldSendNotification('newProducts')) return;
    
                        const newProducts = await this.fetchNewProducts();
                        if (newProducts.length > 0) {
                            sendNotification('新品上架', `今天有${newProducts.length}款新品上架啦!`);
                        }
                    },
                    null,
                    true,
                    'Asia/Shanghai'
                )
            );
    
            // 限时优惠提醒 - 每天中午 12 点和晚上 8 点
            this.jobs.push(
                new CronJob(
                    '0 0 12,20 * * *',
                    async () => {
                        if (!this.shouldSendNotification('promotions')) return;
    
                        const promotions = await this.fetchActivePromotions();
                        if (promotions.length > 0) {
                            sendNotification('限时优惠', '有新的限时优惠活动,点击查看详情!');
                        }
                    },
                    null,
                    true,
                    'Asia/Shanghai'
                )
            );
    
            // 购物车提醒 - 每周五下午 5 点提醒周末特价
            this.jobs.push(
                new CronJob(
                    '0 0 17 * * 5',
                    async () => {
                        if (!this.shouldSendNotification('cartReminder')) return;
    
                        const cartItems = await this.fetchUserCart();
                        if (cartItems.length > 0) {
                            sendNotification('周末将至', '别忘了查看购物车中的商品,周末特价即将开始!');
                        }
                    },
                    null,
                    true,
                    'Asia/Shanghai'
                )
            );
    
            console.log('通知系统已初始化');
        }
    
        async shouldSendNotification(type) {
            const user = getUser();
            if (!user) return false;
    
            const preferences = await getUserPreferences();
            return preferences?.[type] === true;
        }
    
        // 其他方法...
    
        stopAll() {
            this.jobs.forEach(job => job.stop());
            console.log('所有通知任务已停止');
        }
    }
    
    export const notificationManager = new NotificationManager();
    

    写在最后

    作为前端开发者,我们的工作不只是构建漂亮的界面,还需要处理各种复杂的交互和时序逻辑。

    npm 的 cron 包为我们提供了一种专业而灵活的方式来处理定时任务,特别是在 Node.js 环境下运行的前端应用(如 SSR 框架、Electron 应用等)。

    它让我们能够用简洁的表达式设定复杂的执行计划,帮助我们构建更加智能和用户友好的前端应用。

    hkingstu
        1
    hkingstu  
       3 天前
    这个 cron 库看起来 hin 实用
    lisxour
        2
    lisxour  
       3 天前
    我用的是这个,croner
    fov6363
        3
    fov6363  
       3 天前
    如果 node 服务署多个的话,可能要考虑抢占式运行,不了每个服务都运行定时任务有时候会有问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   966 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 22:16 · PVG 06:16 · LAX 15:16 · JFK 18:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.