Not sure if async and promises are necessary in Google API call

Hello everyone,

I have a problem I’ve been trying to solve for a few days now. I feel like I’ve gone down the async/await and Promise rabbit hole unnecessarily, mainly because I can’t tell what I’m doing wrong.

I have a server method that calls the Google Calendar API and inserts a booking.

Biggest problem is it takes a while for the Calendar API service to return the data, and I want to return the url link to the calendar item that only gets generated once the calendar item is created, after which its emailed off from my server.

so right now, it looks like this:

Meteor Method:

sendBooking(importantstuff)  {
let myevent = {importantstuff};
...
if (importantstuff)
 {
   async function getInsert(thisevent){
   let myInsert = await calendarPromise(thisevent);
   await console.log('myInsert: ' + myInsert);
  }
  getInsert(myevent);
}

External Method:

function calendarPromise(myevent){
  console.log('calendarPromise called!');
  return new Promise((resolve, reject) => {
    const SCOPES = ['https://www.googleapis.com/auth/calendar'];
    const SERVICE_ACCOUNT_FILE = require('./calendar_service.json');
    let jwtClient = new google.auth.JWT(SERVICE_ACCOUNT_FILE.client_email,null,SERVICE_ACCOUNT_FILE.private_key,SCOPES);
    jwtClient.authorize(function(err, tokens){
      if(err){ console.log(err); return; }
    });
    const calendar = google.calendar('v3');
    const calendarId = 'myCalendarId@group.calendar.google.com';
    let result = calendar.events.insert({auth: jwtClient,calendarId: calendarId,resource: myevent},
      (err, event) => { if (err) { return ('Custom Error:' + err); }
                        else
                        {
                          let htmlLink = event.data.htmlLink;
                          console.log(htmlLink);
                          return (htmlLink);
                        }
                  });
      resolve(result);
  });
}

the results always looks like this:

? calendarPromise called!
? myInsert undefined
? https:/ /www.google.com/calendar/event?eid=abcdefgh1234....

why is the variable in the getInsert function not waiting to be loaded with the event.data.htmlLink that I want?

I think the issue is you have tried to overcomplicate your method. Given calendarPromise returns a Promise and can be resolved with await, it’s only really necessary to use an async method (and remove the internal async function, which just complicates resolution of the Promise). From the code you’ve shared, something like this:

Meteor.methods({
  async sendBooking(importantstuff) {
    let myevent = {
      importantstuff
    };
    //...
    if (importantstuff) {
      const myInsert = await calendarPromise(myevent);
      console.log('myInsert: ' + myInsert);
    }
  },
});

You also seem to be mixing myevent and thisevent.

Also in your calendarPromise function, you aren’t resolving the promise with the value that you want. You should be resolving with the result in the callback (htmlLink)

OR:
Because googleapis return promises if you don’t pass a callback (docs), you can simplify the function a lot by just returning the promise from calendar.events.insert

async function calendarPromise(myevent){
  console.log('calendarPromise called!');
  const SCOPES = ['https://www.googleapis.com/auth/calendar'];
  const SERVICE_ACCOUNT_FILE = require('./calendar_service.json');
  let jwtClient = new google.auth.JWT(SERVICE_ACCOUNT_FILE.client_email,null,SERVICE_ACCOUNT_FILE.private_key,SCOPES);
  await jwtClient.authorize();

  const calendar = google.calendar('v3');
  const calendarId = 'myCalendarId@group.calendar.google.com';
  return calendar.events.insert({
    auth: jwtClient,
    calendarId: calendarId,
    resource: myevent
  });
}

Also note that we actually wait for the jwtClient to complete authorization by using await jwtClient.authorize();

Thank you for the reply. I took your advice and the advice of @coagmano below, and I simplified my code a lot. (@coagmano i actually had no idea the google apis returned promises) The killer for me is the console.log('myInsert: ’ + myInsert) is still undefined when returned. I’m also pretty sure It’s being called way too soon. I don’t want it to be called until myInsert has a returned value in it, which I thought was the point of await.

I20190319-07:01:09.689(-7)? sendBooking called
I20190319-07:01:09.975(-7)? sendBooking: jwtClient Successfully connected!
I20190319-07:01:10.010(-7)? calendarPromise called!
I20190319-07:01:10.044(-7)? myInsert: undefined
I20190319-07:01:10.166(-7)? calendarPromise: jwtClient Successfully connected!

As you can see, myInsert console log is being called well before the calendarPromise function even authorizes it’s connection. How do I get it to wait until it has a value before it fires off with async/await without having to use setTimeout or setInterval?

Try wrapping your await in try/catch. That’s something you should always do anyway:

try {
  const myInsert = await calendarPromise(myevent);
  console.log('myInsert: ' + myInsert);
} catch (error) {
  console.log(error.message);
}

At this point you should attach a debugger and run through it line by line to test assumptions
calendar.event.insert is supposed to return a promise, does it?
Does that promise resolve to an event? or is it resolving undefined?
Is authorization finishing successfully before the calendar call? (The docs I saw all used different auth methods to you)
etc.

@robfallows i tried that, no errors being returned.

@coagmano That’s because most tutorials use user authentication, I’m using a service account for authorization.

Anyways I believe I have my main problem pinned down, but unsure how to work around it. The way I’m dealing with the google.calendar.insert command is not returning the value that I ask it to return. So, to test, it now looks like this:

let insertResult = calendar.events.insert(
  {auth: jwtClient,calendarId: calendarId,resource: myevent},
  async (err, event) => {
    if(err){console.log(err); return;}
    else
    {
      let myResult = "here is a response";
      return myResult;
    }
  });
  setInterval(()=>{console.log(JSON.stringify(insertResult))}, 1000);

And these are the results:

I20190325-06:36:39.171(-7)? sendBooking: jwtClient Successfully connected!
I20190325-06:36:40.145(-7)? undefined
I20190325-06:36:41.146(-7)? undefined
I20190325-06:36:42.146(-7)? undefined
I20190325-06:36:43.148(-7)? undefined
I20190325-06:36:44.149(-7)? undefined
I20190325-06:36:45.149(-7)? undefined

So I don’t know if I’m even using the insert method correctly, or if my syntax is wrong or what. I could always load an external variable and then run a setInterval to check when it’s value has changed and return it then, but I’m really trying to utilize the async/await pattern that is supposed to eliminate that need.

I’m just not familiar enough with the Google API to offer much in the way of constructive help. @coagmano is your best bet here. However, if it returns Promises, then it will work with Meteor and async / await. Most issues I’ve seen fall into one of three types:

  1. Those which over-complicate what actually needs doing to work with async / await and Promises.
  2. Those which fall into the trap of expecting an async function to return a value (they return a Promise).
  3. Those which expect program execution to wait for an async function to complete before carrying on (it doesn’t).