使用Google API时出现Cannot destructure property 'data'错误的解决问询
我帮你拆解下问题根源,再给出针对性的修复方案:
问题出在哪?
1. API回调的解构错误
你在gmail.users.labels.list的回调里直接写了(err, { data }),但当API返回错误时,第二个参数是undefined——这时候去解构data就会触发Cannot destructure property 'data' of 'undefined'的错误。Google API的回调内部会把这种未捕获的错误转化为Promise拒绝,也就是你看到的警告。
2. 回调参数没传对
你的listLabels函数需要两个参数:auth和callback,但看调用链:test(listLabels) → authorize(..., methodname) → callback(oAuth2Client)——这里的callback就是listLabels,但只传了auth,导致listLabels里的callback是undefined。当你调用callback()时,本质是调用一个不存在的函数,这又会引发新的错误,加重了Promise拒绝的问题。
3. Async流程没衔接好
async.series里的getStuff函数调用了异步的test,但没等test执行完就去判断interimlabels,而且没在异步操作完成后调用async的回调。这就导致async以为任务还没完成,程序就挂起了;而之前的listLabels里调用callback()(虽然是undefined),反而误打误撞推进了流程,但带来了警告。
修复方案
我把你的代码做了针对性修改,关键改动点都标了注释:
var async = require('async-kit'); const fs = require('fs'); const readline = require('readline'); const { google } = require('googleapis'); const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']; const TOKEN_PATH = 'credentials.json'; // 修改test函数,新增callback参数,用于衔接async流程 function test(methodname, callback) { console.log('called'); fs.readFile('client_secret.json', (err, content) => { if (err) { console.log('Error loading client secret file:', err); return callback(err); // 将错误传递给async } // 把async的callback传递给authorize,最终传给listLabels authorize(JSON.parse(content), (auth) => methodname(auth, callback)); }); } /** * Create an OAuth2 client with the given credentials, and then execute the * given callback function. * @param {Object} credentials The authorization client credentials. * @param {function} callback The callback to call with the authorized client. */ function authorize(credentials, callback) { console.log('authorized'); const { client_secret, client_id, redirect_uris } = credentials.installed; const oAuth2Client = new google.auth.OAuth2( client_id, client_secret, redirect_uris[0] ); // Check if we have previously stored a token. fs.readFile(TOKEN_PATH, (err, token) => { if (err) return getNewToken(oAuth2Client, callback); oAuth2Client.setCredentials(JSON.parse(token)); callback(oAuth2Client); }); } /** * Get and store new token after prompting for user authorization, and then * execute the given callback with the authorized OAuth2 client. * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for. * @param {getEventsCallback} callback The callback for the authorized client. */ function getNewToken(oAuth2Client, callback) { const authUrl = oAuth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, }); console.log('Authorize this app by visiting this url:', authUrl); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); rl.question('Enter the code from that page here: ', (code) => { rl.close(); oAuth2Client.getToken(code, (err, token) => { if (err) return callback(err); oAuth2Client.setCredentials(token); // Store the token to disk for later program executions fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => { if (err) return console.error(err); console.log('Token stored to', TOKEN_PATH); }); callback(oAuth2Client); }); }); } /** * Lists the labels in the user's account. * * @param {google.auth.OAuth2} auth An authorized OAuth2 client. * @param {function} callback Async流程的回调函数 */ function listLabels(auth, callback) { const gmail = google.gmail({ version: 'v1', auth }); // 先判断err,再处理data,避免解构undefined gmail.users.labels.list({ userId: 'me' }, (err, response) => { if (err) { console.log('The API returned an error: ' + err); return callback(err); // 把错误传给async } // 现在response肯定存在,再解构data const { data } = response; const labels = data.labels; console.log('got labels'); if (labels.length) { interimlabels = labels; labels.forEach((label) => { console.log(`- ${label.name}`); }); } else { console.log('No labels found.'); } callback(null, labels); // 通知async任务完成,传递结果 }); } var interimlabels = []; async.series([ function getStuff(callback) { // 把async的callback传给test,最终传到listLabels test(listLabels, callback); // 移除原来的同步判断,因为listLabels是异步的,这里判断没用 } ]) .exec(function(error, results) { if (error) { console.log('Error occurred:', error); } else { console.log('done'); console.log('labels', results[0]); // results里存的是listLabels返回的labels } });
关键改动说明:
- 修复API回调:把
(err, { data })改成(err, response),先判断err,再从存在的response中解构data,彻底避免undefined解构错误。 - 传递正确的回调:修改
test函数接收callback参数,然后通过authorize把这个callback传递给listLabels,确保listLabels里的callback是async.series的任务回调,用来推进流程。 - 衔接Async流程:
getStuff里直接调用test(listLabels, callback),把async的回调传给test,让listLabels完成后调用这个回调,通知async任务结束,解决程序挂起问题。 - 错误处理优化:所有异步操作的错误都通过回调传递给
async.series的最终处理函数,便于统一排查问题。
这样修改后,程序既能正确输出邮箱标签,又不会有未处理Promise拒绝警告,也不会挂起。
内容的提问来源于stack exchange,提问作者jhonathan myers

