Reputation: 46425
As an exercise I am trying to convert https://developers.google.com/sheets/api/quickstart/nodejs from a callback style to a promises style and then refactor it into an async/await using util.promisify
.
Everything went fine until the final function. The original is:
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
console.log('Name, Major:');
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[4]}`);
});
} else {
console.log('No data found.');
}
});
}
The promisified version is:
function listMajors(auth) {
const sheets = google.sheets({version: 'v4', auth});
const getValues = util.promisify(sheets.spreadsheets.values.get);
getValues({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}).then(function (res) {
const rows = res.data.values;
if (rows.length) {
// Print columns A and E, which correspond to indices 0 and 4.
rows.map((row) => {
console.log(`${row[0]}, ${row[1]}`);
});
}
}).catch(err => console.log('The API returned an error: ' + err));
}
The original returns the spreadsheet. The promisified version says, The API returned an error: TypeError: Cannot read property 'context' of undefined
. I can't figure out why they are different.
Upvotes: 3
Views: 708
Reputation: 707606
Your promisified version is probably losing the parent object of the method you're trying to call.
If you change it to this, that issue should be fixed:
let values = sheets.spreadsheets.values;
values.getP = util.promisify(sheets.spreadsheets.values.get);
values.getP().then(...);
What's important here is that when calling your promisified version, you're still using the values
object reference as in values.getP()
so that the promisified function still gets the proper this
value.
FYI, you could probably also bind the proper object to the method:
const getValues = util.promisify(sheets.spreadsheets.values.get).bind(sheets.spreadsheets.values);
Here's a simple example to demonstrate:
class Test {
constructor() {
this.data = "Hello";
}
demo() {
console.log(this);
}
}
let t = new Test();
// this works just fine
t.demo();
// this doesn't work because x has lost the t object
// so when you call x(), it no longer has a reference to the object instance
// It needs to be called like obj.method()
let x = t.demo;
x();
This comes up so often, it would be nice to have this snippet of code handy:
const util = require('util');
if (!util.promisifyMethod) {
util.promisifyMethod = function(fn, obj) {
return util.promisify(fn).bind(obj);
}
}
Then, anytime you want to promisify a method of some object, you could use it:
const getValues = util.promisifyMethod(sheets.spreadsheets.values.get, sheets.spreadsheets.values);
Upvotes: 6