You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:01:12