btilly
btilly

Reputation: 46425

What is wrong with this promisify?

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

Answers (1)

jfriend00
jfriend00

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

Related Questions