17

So I've happily been using async/await since node 8 is supported on Firebase Cloud Functions. I am struggling with 1 thing though. When using callable functions, it is told that you have to return a promise in the function, otherwise it won't work correctly. When using raw promises, its clear to me how to use it:

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return promiseMethod().then((result) => {
        return nextPromise(result);
    }).then((result) => {
        return result;
    }).catch((err) => {
        // handle err
    })
});

But now, with async await, I'm not sure how to return this "chain of promises":

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    const res2 = await nextPromise(res1);
    return res2;
    // ??? Where to return the promise?
});

Does somebody know?

Jaap Weijland
  • 3,146
  • 5
  • 23
  • 31
  • 2
    "I'm not sure how to return this "chain of promises":" — Exactly as you have done in the code you wrote. – Quentin Aug 21 '18 at 13:18
  • Alright, but the logs on the firebase console tells me that I am calling the function 2 times, when I am actually calling it only 1 time. I thought this had something to do with returning these promises the wrong way. – Jaap Weijland Aug 21 '18 at 13:21
  • 1
    [Drop the pointless `.then((result) => { return result; })`](https://stackoverflow.com/q/41089122/1048572) – Bergi Aug 21 '18 at 14:10
  • You can easily set up an example project and verify the claims of the answers. Just create a function which will wait 10 Seconds and then return a result. If you call this function with await, the firebase function will correctly wait 10 seconds and then return the result. – Falco Jan 08 '21 at 09:53
  • I have added an authorative code-sample by the google-firebase team to my answer. If that is not enough to support it, I don't know. – Falco Jan 08 '21 at 09:59
  • You never accepted a solution wondering what the correct answer was – FabricioG Apr 12 '22 at 22:05

11 Answers11

4

HTTP functions don't return a promise. They just send a result. You still have to use promises correctly in order to send the result, but a return value is not required. HTTP functions are terminated when the response is sent. See the documentation for more details:

Terminate HTTP functions with res.redirect(), res.send(), or res.end().

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Is this also the case with .onCall-functions? When I call my function once, the logger on the firebase control panel says my function is calles twice. I reckoned it had something to do with returning these promises. – Jaap Weijland Aug 21 '18 at 08:11
  • No, callable functions require you to return a promise that resolves with the data you want to send to the client. This should be clear from the documentation. – Doug Stevenson Aug 21 '18 at 13:05
  • Exactly, so my initial question applies with callable functions: how to return this promise when using async/await – Jaap Weijland Aug 21 '18 at 13:14
  • 6
    Well, you edited the question to show a callable function instead of an HTTP function you had originally. You're effectively asked a different question in place of the first, which is not really the way Stack Overflow works. If you completely change the terms of the question, you will confuse everyone. – Doug Stevenson Aug 21 '18 at 13:23
  • I thought HTTPS also needed to have a promise returned; for me it was a minor detail, but point noted. – Jaap Weijland Aug 21 '18 at 13:56
  • 1
    HTTPS and callable are very different in the way they send data back to the client. Please take some time to study the documentation and samples to get that clarified. – Doug Stevenson Aug 21 '18 at 14:02
  • @DougStevenson The firebase doc states that you can return a promise for HTTP-functions and just have to make sure that this promise will eventually call `response.end()` or something equivalent – Falco Apr 15 '19 at 08:11
1

"await" is just syntax sugar for returning a Promise

When you write an async function, the code will actually exit the function and return a Promise at the first await it encounters. All code after the await will be converted to a then().

So for firebase writing code with async/await is perfectly save and in my experience even less error-prone, since I can more easily structure try&catch in my code!

Proof:

Just run this in your console:

async function iAmAsync() {
  await new Promise(r => window.setTimeout(r, 1000))
  return 'result'
}

let x = iAmAsync()
console.log(x)

Will print: Promise{<resolved>: "result"}

TL;DR: You don't need to change anything - if you write code with multiple awaits, this will be handled by firebase like a chain of promises and everything will just work.

And since my answer was downvoted, here is an authorative code-sample by the google firebase team itself:

https://github.com/firebase/functions-samples/blob/master/quickstarts/uppercase/functions/index.js

exports.addMessage = functions.https.onRequest(async (req, res) => {
// [END addMessageTrigger]
  // Grab the text parameter.
  const original = req.query.text;
  // [START adminSdkPush]
  // Push the new message into the Realtime Database using the Firebase Admin SDK.
  const snapshot = await admin.database().ref('/messages').push({original: original});
  // Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
  res.redirect(303, snapshot.ref.toString());
  // [END adminSdkPush]
});
Falco
  • 3,287
  • 23
  • 26
  • This answer assumes onRequest the question is for onCall – FabricioG Apr 12 '22 at 22:04
  • 1
    @FabricioG but the syntax is the same. See https://github.com/firebase/functions-samples/blob/main/fulltext-search-firestore/functions/elastic.js as an official example from the firebase team with onCall – Falco Apr 14 '22 at 07:40
  • Not really onCall receives data, context and returns a return statement not a res. in link you posted: functions.https.onCall(async (data, context) not req, res @Falco – FabricioG Apr 14 '22 at 17:33
  • @FabricioG The question is not about how to use async/await and how to return a promise. The onCall function is just an exampleby the OP. The problem and the solution is the same for both methods. And the links I provided show that. – Falco Apr 14 '22 at 17:36
1

You nailed it with your example code.

Async/await is just a newer way of promise. They can be used interchangeable.

Here is an example promise and async/await of the same function.

This

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return promiseMethod().then((result) => {
        return nextPromise(result);
    }).catch((err) => {
        // handle error here
    })
});

is equivalent to this:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
  try {
    const result = await promiseMethod();
    return nextPromise(result); // You are returning a promise here
  }catch(e) {
    // handle error here
  }
});

Note that in both cases, you are returning a promise at the end. The return value of this onCall function would be whatever nextPromise(result) is. Since you are returning nextPromsie(result), you don't need to await it.

dshukertjr
  • 15,244
  • 11
  • 57
  • 94
1

To see the code solution to your question look at the answer of dshukertjr.

If you want to understand how to return a "chain of promises" with async/await, here is your answer:

You cant ! Why ? Because await is used to wait for a Promise to complete. Once await return a value their is no more Promise.

So if you absolutely want to return a promise using await, you can wait for one of the two functions that return promises but not both.

Here is two way to do that:

A :

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    try {
        const result = await promiseMethod();
        return nextPromise(result); // You are returning a promise here
    }catch(e) {
        // handle error here
    }
});

B:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    return promiseMethod().then(async (result) => {
        return await nextPromise(result);
    }).catch((err) => {
        // handle err
    })
});

The only difference between A and B is that A waits for "PromiseMethod" to complete before returning a Promise. Whereas B returns a Promise right after being called.

julesl
  • 311
  • 2
  • 4
0

Seems, we have to wait for several Promises in way like this:

const listOfAsyncJobs = [];
listOfAsyncJobs.push(createThumbnail(1, ...));
listOfAsyncJobs.push(createThumbnail(2, ...));
listOfAsyncJobs.push(createThumbnail(3, ...));
...
return Promise.all(listOfAsyncJobs); // This will ensure we wait for the end of the three aync tasks above.
Kirill Vashilo
  • 1,559
  • 1
  • 18
  • 27
0

From async method whatever you return it gets wrapped in promise automatically. e.g

const myFun = async () => {return 5}

 myFun();


// Output in the console
Promise {<fulfilled>: 5}

And you can chain with the returned result since it is a promise

Another example with enhancement as suggested in other answer

 const myFun4 = async () => {
      const myNum = await new Promise(r => window.setTimeout(() => r(5), 1000));
      const myNum2 = await new Promise(r => window.setTimeout(() => r(5), 1000));
      return myNum + myNum2;
    }
    myFun4().then((n) => console.log(n));
    // Output
    10
tsfahmad
  • 404
  • 4
  • 12
0

The return value of async-await function is Promise. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#return_value

So, what you did actually is returning a chain of promises.

const nextPromise = () => {
    console.log('next promise!');
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('next promise result')
        }, 3000)
    });
}

const promiseMethod = () => {
    console.log('promise!');
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('promise result');
        }, 2000)
    });
}

exports.createBankAccount = functions.https.onCall((data, context) => {
    return promiseMethod().then((result) => {
        return nextPromise(result);
    }).then((result) => {
        return result;
    }).catch((err) => {
        // handle err
        console.log(err);
    })
});


exports.createBankAccountAsync = functions.https.onCall(async (data, context) => {
    const result = await promiseMethod();
    const res = await nextPromise(result);
    return res;
});

I have created test project on firebase and both function calls give same logs. enter image description here

Richard Zhan
  • 460
  • 3
  • 10
0

A solution in that case is Promise.all().

    exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const promises = [];

    const res1 = await promiseMethod();
    const res2 = await nextPromise(res1);

    promises.push(res1);
    promises.push(res2);

    // Here's the return of the promises
    return Promise.all(promises).catch(error => console.error(error));
});

You may find more informations about promises in this article on freecodecamp.org/promise-all

Everton Costa
  • 570
  • 5
  • 16
0

Solution

For an alternative method, you can use Promise.allSettled(). It is the best way to wait for all promises in the function to complete as well as provide an easy way to modify the final return.

Excerpt from the documentation

The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.

It is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully, or you'd always like to know the result of each promise.

Your updated code should be

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    const res2 = await nextPromise(res1);
    return Promise.allSettled([res1, res2]).then((results) => results.forEach((result) => console.log(result.status)));
});

Additional Info

You should also look into Promise object, it has some nice methods for such a situation. Read more at documentation link

Eagnir
  • 459
  • 3
  • 7
-1

Since you need to return promise you can create the promise object and resolve/reject (return) your response from api after processing all the promises.

Option 1:

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return new Promise(async (resolve, reject) => {
        try {
            const res1 = await promiseMethod();
            const res2 = await nextPromise(res1);
            // This will return response from api
            resolve(res2);
        }
        catch (err) {
            // Handle error here
            // This will return error from api
            reject(err)
        }
    })
});

Option 2:

exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
    return new Promise(async (resolve, reject) => {
        const res1 = await promiseMethod();
        const res2 = await nextPromise(res1);
        // This will return response from api
        resolve(res2);
    })
        .then((val) => val)
        .catch((err) => {
            // Handle error here
            // This will return error from api
            return err
        })
});
Darshan Jain
  • 239
  • 5
  • 14
-2

Just convert to a Promise if required.

I.e. If nextPromise returns a Promise:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    return nextPromise(res1);
});

On the other hand, if nextPromise is an async function, just convert it to a Promise:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    return Promise.resolve(nextPromise(res1));
});

you can also convert the result:

exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
    const res1 = await promiseMethod();
    const res2 = await nextPromise(res1);
    return Promise.resolve(res2);
});
Erez
  • 65
  • 3
  • This is completey unnesseccary: An async function will return a Promise to the caller at the first "await", which will eventually resolve to whatever you return in the return statement. Wrapping this promise in another promise achives nothing. – Falco Apr 15 '19 at 08:08
  • I gave 3 examples. the first is optimal, the other two are not, and are there just to clarify there is more than one way to do the same thing. – Erez Apr 16 '19 at 12:02
  • Promise.resolve is completely unnecessary and misleading. You can just remove it in both cases, without any downsides. It is just unnecessary code distracting from the way async functions work – Falco Apr 16 '19 at 16:03