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

Node.js中如何在回调完成后获取Google API联系人全量数据?

解决Node.js调用Google People API异步导致返回空数据的问题

问题根源

  1. 路由中fs.readFile的回调函数未声明为async,内部的await authorize(...)无法生效,导致res.send(full_person_array)在数据获取前就执行。
  2. authorize函数调用异步的listConnectionNames后立即resolve(),此时联系人数据还未写入全局数组。
  3. 依赖全局变量full_person_array存储数据,异步场景下无法保证数据已准备完成。

解决方案:全Promise/Async-Await重构

将所有回调式异步操作改为Promise风格,通过async/await严格控制执行顺序,避免全局变量依赖。

1. 重构路由函数

app.get('/contacts', async function(req, res) {
  try {
    // 用Promise版fs读取凭证文件
    const content = await fs.promises.readFile('credentials.json');
    const credentials = JSON.parse(content);
    
    // 等待授权完成,获取认证客户端
    const authClient = await authorize(credentials);
    
    // 等待联系人数据获取完成
    const contacts = await listConnectionNames(authClient);
    
    // 数据准备好后再响应
    res.send(contacts);
  } catch (err) {
    console.error('获取联系人失败:', err);
    res.status(500).send('获取联系人失败,请检查日志');
  }
});

2. 修改authorize函数(移除回调,直接返回认证客户端)

function authorize(credentials) {
  return new Promise((resolve, reject) => {
    const { client_secret, client_id, redirect_uris } = credentials.installed;
    const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]
    );

    // 用Promise版fs读取token文件
    fs.promises.readFile(TOKEN_PATH)
      .then(token => {
        oAuth2Client.setCredentials(JSON.parse(token));
        resolve(oAuth2Client);
      })
      .catch(err => {
        // 无token时获取新token,需确保getNewToken返回Promise
        getNewToken(oAuth2Client)
          .then(() => resolve(oAuth2Client))
          .catch(reject);
      });
  });
}

// 补充:将getNewToken改为Promise版本(适配原有逻辑)
function getNewToken(oAuth2Client) {
  return new Promise((resolve, reject) => {
    const authUrl = oAuth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: ['https://www.googleapis.com/auth/contacts.readonly']
    });
    console.log('请访问以下链接授权:', authUrl);

    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });
    rl.question('输入授权页面的验证码: ', (code) => {
      rl.close();
      oAuth2Client.getToken(code, (err, token) => {
        if (err) return reject('获取访问令牌失败:', err);
        oAuth2Client.setCredentials(token);
        // 保存token到文件
        fs.promises.writeFile(TOKEN_PATH, JSON.stringify(token))
          .then(() => resolve())
          .catch(reject);
      });
    });
  });
}

3. 修改listConnectionNames(返回Promise,直接返回联系人数据)

function listConnectionNames(auth) {
  const service = google.people({ version: 'v1', auth });
  return new Promise((resolve, reject) => {
    service.people.connections.list({
      resourceName: 'people/me',
      pageSize: 50,
      personFields: 'names,emailAddresses,phoneNumbers',
    }, (err, res) => {
      if (err) return reject('API调用错误: ' + err);
      // 无联系人时返回空数组,避免undefined
      const connections = res.data.connections || [];
      console.log('获取到联系人数量:', connections.length);
      resolve(connections);
    });
  });
}

关键改动说明

  • 移除全局变量full_person_array,改为函数内部返回数据,彻底避免异步竞态问题。
  • 所有文件操作改用fs.promises的Promise版本,配合await实现顺序执行。
  • authorize不再依赖回调传递客户端,直接返回Promise,让路由控制后续流程。
  • 用try/catch统一捕获所有异步错误,返回友好的错误响应。

内容的提问来源于stack exchange,提问作者Impunkj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 12:18:25