1

Trying to return the value after google api call in the below async function in google cloud function

 async function getGoogleApiToken() {
      let jwtClient = new google.auth.JWT(
               privatekey.client_email,
               null,
               privatekey.private_key,
               ['https://www.googleapis.com/auth/compute']);
        //authenticate request
       await jwtClient.authorize(function (err, tokens) {
         if (err) {
           console.log(err);
           return;
         } else {
           console.log("Successfully connected!"+ tokens.access_token); //displaying the token correctly
           return tokens.access_token; //returns the token
         }
        });
    }
  exports.consumepromise = async (req,res) =>{
  const tokennew =  await getGoogleApiToken();
  console.log("token"+tokennew);
  }

Error: tokennew is undefined

Not sure where it is going wrong.

Jax
  • 139
  • 2
  • 12
  • You are `await`ing `jwtClient.authorize` but not doing anything with the return value. Your return statement is inside the scope of the callback function you passed to `jwtClient.authorize`, not the scope of your function `getGoogleApiToken`, which returns nothing. – Jake Apr 26 '20 at 17:22
  • so how do i modify the code to work? – Jax Apr 26 '20 at 17:29
  • ` google.auth.JWT` is from which npm package ?? @Jax – ddor254 Apr 26 '20 at 17:44
  • googleapis package. which is working fine – Jax Apr 26 '20 at 17:44

2 Answers2

5

If I had to guess it's because jwtClient.authorize accepts a callback instead of returning a promise -

async function getGoogleApiToken() {
  let jwtClient = new google.auth.JWT(...);
  await jwtClient.authorize(function (err, tokens) {
    if (err) {
      console.log(err);
      return;
    } else {
      console.log("Successfully connected!"+ tokens.access_token); //displaying the token correctly
      return tokens.access_token; //returns the token // <- NO IT DOESN'T
    }
  });
}

Callbacks cannot return a value. This is one of the most popular questions on StackOverflow. Callbacks like the ones used in jwtClient.authorize are just a common usage pattern and do not interop with async and await directly. You must return a Promise from your getGoogleApiToken. One such way is to wrap a Promise around the jwtClient.authorize call -

async function getGoogleApiToken() {
  const jwtClient = new google.auth.JWT(...); // <-- btw use const
  return new Promise((resolve, reject) => // <-- wrap in promise
    jwtClient.authorize((err, tokens) => {
      if (err)
        reject(err) // <-- reject the error
      else
        resolve(tokens) // <-- resolve the result
    })
  )
}

We don't want to add this Promise wrapper every time we wish to use a callback-accepting function with other Promise-based code, including any async functions. Turning any callback-accepting async function into a promise-returning async function can be a mechanical process. In fact, that's what NodeJS's util.promisify does. It works something like this -

// approximation of util.promisify
const promisify = f => (...args) =>
  new Promise ((pass, fail) =>
    f (...args, (err, result) => err ? fail(err) : pass(result))
  )

// example callback-accepting function
const demoCallbackFunc = (a, b, callback) =>
  setTimeout(callback, 1000, null, a + b)

// ordinary call
demoCallbackFunc(3, 5, (err, res) => {
  if (err)
    console.error(err)
  else
    console.log(res)
})

// promisified
promisify(demoCallbackFunc)(3, 5)
  .then(console.log, console.error)

console.log("please wait 1 second...")

Using promisify in your program will clean it up considerably -

async function getGoogleApiToken() {
  const jwtClient = new google.auth.JWT(...);
  const auth = promisify(jwtClient.authorize.bind(jwtClient))
  return auth() // <-- no need for await, auth already returns a promise
}

Or without an intermediate assignment of auth -

async function getGoogleApiToken() {
  const jwtClient = new google.auth.JWT(...);
  return promisify(jwtClient.authorize.bind(jwtClient))()
}

Or without bind using an arrow function -

async function getGoogleApiToken() {
  const jwtClient = new google.auth.JWT(...);
  return promisify(callback => jwtClient.authorize(callback))()
}
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • you made my day :) – Jax Apr 26 '20 at 17:57
  • @Jax Happy to help. See the link I added in paragraph 2 for more insight. All the best :D – Mulan Apr 26 '20 at 17:58
  • @Thankyou In this statement that you have mentioned const auth = promisify(jwtClient.authorize.bind(jwtAuthorize)), what is jwtAuthorize variable here? – Jax Apr 27 '20 at 15:57
  • My apologies for the typo, Jax. It is supposed to be `jwtClient.authorize.bind(jwtClient)`. I fixed it in the post too :) – Mulan Apr 27 '20 at 16:43
0

const tokennew = await getGoogleApiToken(); should be inside an async block or you can use .then and .catch on it like so:

with async block

  async function consumePromise() {
   const tokennew =  await getGoogleApiToken();
   console.log("token"+tokennew);
  }
  // then call it
  consumePromise();

with then and catch

   getGoogleApiToken()
     .then(tokennew => {
       console.log("token"+tokennew);
     })
     .catch(err => {
       console.log(err);
     })

The most important thing to note here is that you can't access a value resolved by a promise outside an async block or without using .then and .catch on the promise.

Ikechukwu Eze
  • 2,703
  • 1
  • 13
  • 18