Node.js中Google Tasks API定时检测新任务及报错排查
解决Google Tasks定时轮询与实时网页推送问题
我来帮你梳理下遇到的问题,一步步搞定定时轮询和实时推送的需求:
一、为什么会出现 TypeError [ERR_INVALID_CALLBACK] 错误?
你写的 setInterval(authorize(JSON.parse(content), listTaskLists), 60000) 犯了一个典型的函数传参错误:你直接执行了authorize函数,并把它的返回值(undefined)传给了setInterval,但setInterval的第一个参数要求是一个函数引用,而不是函数执行后的结果。Node.js识别到你传入的不是合法回调,所以抛出了这个错误。
更重要的是:完全不需要每次轮询都重新走授权流程!第一次授权后,OAuth2客户端会自动用refresh token刷新访问令牌,我们只需要保存授权后的客户端实例,之后重复使用即可。
二、正确的定时轮询+实时推送实现方案
步骤1:全局存储关键实例
先在代码顶部声明全局变量,用于保存授权客户端和已有的任务记录:
let authorizedClient = null; // 保存授权后的OAuth2客户端 let previousTasks = new Map(); // 存储已有任务(key: taskId,用于对比新增任务)
然后修改授权逻辑,保存客户端并启动轮询:
fs.readFile('credentials.json', (err, content) => { if (err) return console.log('Error loading client secret file:', err); const credentials = JSON.parse(content); // 授权成功后保存客户端,首次拉取任务并启动轮询 authorize(credentials, (auth) => { authorizedClient = auth; listTaskLists(auth); // 首次加载所有任务 setInterval(pollTasks, 60000); // 每分钟轮询一次 }); });
步骤2:编写轮询核心函数
创建专门的轮询函数,复用已授权的客户端查询任务:
function pollTasks() { if (!authorizedClient) { console.log('Client not authorized yet, skipping this poll'); return; } // 传入true标记本次是轮询,需要对比新增任务 listTaskLists(authorizedClient, true); }
步骤3:修改任务查询逻辑,识别新增任务
调整listTaskLists和getTasksFromTaskList,加入新旧任务对比,并通过Socket.io推送新增内容:
function listTaskLists(auth, isPolling = false) { const service = google.tasks({version: 'v1', auth}); service.tasklists.list({ maxResults: 10, }, (err, res) => { if (err) return console.error('The API returned an error: ' + err); const taskLists = res.data.items; if (taskLists) { taskLists.forEach((taskList) => { getTasksFromTaskList(taskList.id, auth, isPolling); }); } else { console.log('No task lists found.'); } }); } function getTasksFromTaskList(tasklistid, auth, isPolling) { const service = google.tasks({version: 'v1', auth}); // 轮询时可以加updatedMin参数,只查最近更新的任务,减少数据量 const params = { tasklist: tasklistid, ...(isPolling && { updatedMin: new Date(Date.now() - 60000).toISOString() }) }; service.tasks.list(params, (err, res) => { if (err) return console.error('The API returned an error: ' + err); const tasks = res.data.items || []; if (isPolling) { // 筛选出从未出现过的新任务 const newTasks = tasks.filter(task => !previousTasks.has(task.id)); newTasks.forEach(task => { console.log(`New task detected: ${task.title}`); previousTasks.set(task.id, task); // 通过Socket.io推送给所有连接的网页客户端 io.emit('newTask', { title: task.title, id: task.id, taskListId: tasklistid }); }); } else { // 首次加载时,初始化已有任务列表 tasks.forEach(task => previousTasks.set(task.id, task)); console.log(`Loaded ${tasks.length} tasks from list ${tasklistid}`); } }); }
步骤4:前端接收推送并更新页面
在你的前端页面(client目录下的HTML/JS)中,加入Socket.io监听逻辑:
// 前端示例代码(原生JS) const socket = io(); // 监听新任务推送事件 socket.on('newTask', (task) => { console.log('Received new task:', task); // 这里写更新网页DOM的逻辑,比如把新任务添加到页面列表中 const taskList = document.getElementById('task-container'); const taskItem = document.createElement('div'); taskItem.className = 'task-item'; taskItem.textContent = task.title; taskList.appendChild(taskItem); });
三、轮询方案是否合适?
对于Google Tasks来说,轮询是目前最可行的实时同步方案,因为官方并没有提供Webhook来主动通知任务变化。需要注意几个细节:
- API配额限制:Google Tasks API有免费调用配额,个人使用完全足够,但避免把轮询间隔设得太短(比如低于30秒),防止触发配额限制。
- 性能优化:上面代码中已经加入了
updatedMin参数,轮询时只拉取最近更新的任务,能有效减少数据传输量和API调用开销。 - 容错处理:可以在轮询函数中加入错误捕获,比如网络请求失败时重试,避免单次错误导致整个轮询流程中断。
这样修改后,你就能实现定时轮询Google Tasks并实时推送新增任务到网页的功能了。
内容的提问来源于stack exchange,提问作者BlackBruceLee
相关产品推荐
相关产品推荐

