Asynchronous Programming (I Promise!) with Cloud Functions for Firebase - Firecasts

By: Firebase

336   2   18416

Uploaded on 04/25/2017

Documentation: https://goo.gl/1qBuce
Codelab: https://goo.gl/WZmtze
Sample code: https://goo.gl/UQWJzb

Cloud Functions for Firebase lets you run managed backend code for your mobile app that extends and connects Firebase features. In this Firecast, Jen will show you how to properly use promises for async programming with Cloud Functions.

Add the Firecasts playlist! https://goo.gl/n2XqG1

Subscribe to the Firebase Channel: https://goo.gl/9giPHG

Comments (9):

By anonymous    2017-09-20

Solved: Sorry, I misunderstood the API a bit. I should have watched the promise video first!

I needed to put

return rootRef.update...

instead of

rootRef.update...

Original Thread

By anonymous    2017-09-20

You can structure your code in a way that doesn't require optional values, as below:

  if (statement){
    pickerActive = true

     const prom3 = pathToLottery.once('value') 

    prom3.then(snap => {
    // some func that has access to data at pathToLottery
    const data = snap.val()
    return 
    })

  } else {
  return
  }

Check out this video for more info: https://www.youtube.com/watch?v=NgZIb6Uwpjc&t=35s

Original Thread

By anonymous    2017-09-20

You can't just call .val() on a database reference and expect to get the data at that location. You need to add a value listener in order to get new data.

Fortunately, this is fully supported inside of Cloud Functions:

exports.newCloudFunction = functions.database.ref('/user/{userId}/sources/saveSource').onWrite(event => {
    // Retrieve true/false value to verify whether card should be kept on file
    const saveSource = event.data.val();

    if (saveSource) {
        const customerIdRef = event.data.adminRef.parent.child('customer_id')
        // attach a 'once' value listener to get the data at this location only once
        // this returns a promise, so we know the function won't terminate before we have retrieved the customer_id
        return customerIdRef.once('value').then(snap => {
            console.log(customer_id);
            // use customer_id here
        });
    } 
});

You can learn more here.

Original Thread

By anonymous    2017-10-15

I'm not sure how you want Cloud Functions to be triggered in this case. I'll assume you want a HTTP trigger, so that you can simply call it from the browser or a web hook.

That means you start with a basic HTTP-triggered function:

exports.updateStatus = functions.https.onRequest((req, res) => {
  // ...
  res.status(200).send("done");
});

Next up you'll need to access the Realtime Database within this function. To do that you'll use the Firebase Admin SDK, which gives you easy administrative access within your code:

const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.updateStatus = functions.https.onRequest((req, res) => {
  // ...
  res.status(200).send("done");
});

Then we get to the actual code to change the data. This is standard database access code and has little to do with Cloud Functions. In this case you want to query for timestamp, loop over the results, and set the active property:

const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.updateStatus = functions.https.onRequest((req, res) => {
  let now = Date.now();
  let query = admin.database().ref("events").orderByChild("timestamp").startAt(now);
  query.once("value").then(function(snapshot) {
    var promises = [];
    snapshot.forEach(function(child) {
      promises.push(child.ref.update({ active: false }));
    })
    Promise.all(promises).then(function() {
      res.status(200).send("done");
    });
  });
});

That last code is a bit tricky, since it deals with many asynchronous write operations. Cloud Functions will terminate your function after your send the response to the client, so it's important that you only send a response back after all those asynchronous writes are done. I use a Promise.all() for that. For more information on this asynchronous nature, read the documentation, this blog post, and watch this video.

For more info, I'd recommend studying the documentation for the Firebase Admin SDK.

Original Thread

By anonymous    2017-10-22

I have following sample function from this tutorial: Asynchronous Programming (I Promise!) with Cloud Functions for Firebase - Firecasts

exports.emailEmployeeReport = functions.database
.ref('/employees/${eid}/reports/${rid}')
.onWrite(event => {
    const eid = event.params.eid;
    const report = event.data.val().report;
    const root = event.data.ref.root;
    const mgr_promise = root.child(`/employees/${eid}/manager`).once('value');
    const then_promise = mgr_promise.then(snap => {
        const mgr_id = snap.val();
        const email_promise = root.child(`/employees/${mgr_id}/email`).once('value');
        return email_promise;
    }).catch(reason => {
        // Handle the error
        console.log(reason);
    });;
    const then_promise2 = then_promise.then(snap => {
        const email = snap.val();
        const emailReportPromise = sendReportEmail(email, report);
        return emailReportPromise;
    }).catch(reason => {
        // Handle the error
        console.log(reason);
    });
    return then_promise2;
});

var sendReportEmail = function (email, report) {
    const myFirstPromise = new Promise((resolve, reject) => {
        // do something asynchronous which eventually calls either:
        //
        setTimeout(function () {
            try {
                var someValue = "sendReportEmail";
                console.log(someValue);
                // fulfilled
                resolve(someValue);
            }
            catch (ex) {
                // rejected
                reject(ex);
            }
        }, 2000);
    });
    return myFirstPromise;
}

once I run firebase deploy command, eventually I am getting following error:

functions[emailEmployeeReport]: Deploy Error: Failed to configure trigger providers/google.firebase.database/eventTypes/ref.write@firebaseio.com (emailEmployeeReport)

I also have a simple hello-world method and a similar trigger method, and they deploy fine.

Am I missing something here?

Original Thread

By anonymous    2017-10-30

You're not returning a promise, which means that Cloud Functions may terminate your code before it has written to the database, or that it may keep it running (and thus charge you) longer than needed. I recommend reading more about that here or watching this video.

The simple fix is quite simple in your case:

exports.fanOutLink = functions.database.ref('/users/PdXHgkfP3nPxjhstkhX/link').onWrite(event => {
    friendsListRef = admin.database().ref('/friendsList');

    if (!event.data.val()){
        return; // terminate the function  
    }else{
        let linkURL = event.data.val()
        return friendsListRef.once("value", function(snap)){
            var promises = [];
            snap.forEach(function(childSnapshot)){
               let childKey=childSnapshot.key; 
    promises.push(admin.database().ref('/friendsList/'+childKey+'/PdXHgkfP3nPxjhstkhX/link').set(event.data.val()));
            }          
            return Promise.all(promises);
        }
    }
})

You'll see that I'm mostly just passing the return value of once() and the combined set() calls back up (and out of) our function, so that Cloud Functions knows when you're done with it all.

But please study the post, video and other materials thoroughly, because this is quite fundamental to writing Cloud Functions.

If you are new to JavaScript in general, Cloud Functions for Firebase is not the best way to learn it. I recommend first reading the Firebase documentation for Web developers and/or taking the Firebase codelab for Web developer. They cover many basic JavaScript, Web and Firebase interactions. After those you'll be much better equipped to write code for Cloud Functions too.

Original Thread

Popular Videos 90

Submit Your Video

If you have some great dev videos to share, please fill out this form.