Ben
Ben

Reputation: 138

NodeJS Promise Firebase

Got to love nodeJS and asynchronous nature! With that, I'm dumbfounded how to to continue bc I can't keep nesting promises and of course that's a not go so I'm throwing up my hands bc each step requires a completed action with data from the previous step.

This is what I'm trying to accomplish and code is below.

  1. A new college comes into /sessions/college
  2. After getting the value of that key, go find advisors that subscribe to that college.
  3. Get the FCM tokens for the subscribing advisors
  4. Haven't even gotten to this part obviously, but send a FCM notification to subscribers.
  5. Tada!

exports.newSessionNotifer = functions.database.ref('/sessions/college').onCreate((snap, context) => {
    const college = snap.val(); 
    var promises = [];
    var getAdvisors = admin.database().ref('colleges').child(college).once('value').then((snapshot) => {
        const people = snapshot.val();
        var advisors = Object.keys(people);
        return advisors;
    }).then((advisors) => {
        return advisors.forEach((token) => {
            var advisorToken = admin.database().ref('users').child(token).child('fcmtoken').child('token').once('value');
            return console.log(advisorToken);
        });
    });

    return Promise.all(promises).then((values) => {
        console.log(promises);
        return console.log('Hi');
    });

Upvotes: 0

Views: 871

Answers (2)

danh
danh

Reputation: 62686

You're on the right track. once() returns a promise, and it is the set of promises from repeated calls to once that must be collected and run with Promise.all().

exports.newSessionNotifer = functions.database.ref('/sessions/college').onCreate((snap, context) => {
    const college = snap.val();
    return admin.database().ref('colleges').child(college).once('value');
}).then(snapshot => {
    const people = snapshot.val();
    let advisors = Object.keys(people);
    let promises = advisors.map(token => {
        return admin.database().ref('users').child(token).child('fcmtoken').child('token').once('value');
    });
    return Promise.all(promises);
});

EDIT Editing again, this time with the OP's answer in hand. On style, I'm not sure what lint says, but my definition of bad nesting style is when a then() block contains another then() block. Also regarding style, my approach to making this stuff comprehensible is to build (and test) small functions, one per async task.

On structure, the OP's new answer unnecessarily chains a second block after return advisors. Since advisors isn't a promise, we can carry on from there with synchronous code. Also on structure, the OP's solution creates a series of promises -- two for each advisor (get advisor token and push) -- but these are not certain to complete unless Promise.all is applied and returned.

Summing all that, my advice would be as follows...

On create, get the advisors for the college, send each a message.

exports.newSessionNotifer = functions.database.ref('/sessions/{sessionID}/college').onCreate((snap, context) => {
    const college = snap.val(); 
    return advisorsForCollege(college).then(advisors => {
        let promises = advisors.map(advisor => sendAdvisorMessage(advisor, college));
        return Promise.all(promises);
    });
});

Advisors for a college are apparently the keys from that college object

function advisorsForCollege(college) {
    return admin.database().ref('colleges').child(college).once('value').then(snapshot => Object.keys(snapshot.val()));
}

Sending an advisor message means getting the advisors token and doing a push. Return a two-promise chain that does that...

function sendAdvisorMessage(advisor, college) {
    return tokenForAdvisor(advisor).then(token => {
        let title = `There's a new session for ${college}!`;
        let body = 'Go to the middle tab and swipe right to accept the session before your peers do!'
        return sendToDevice(token, title, body);
    });
}

Now we just need one to get an advisor's token and one to do a push...

function tokenForAdvisor(advisor) {
    return admin.database().ref('users').child(advisor).child('fcmtoken').child('token').once('value');
}

function sendToDevice(token, title, body) {
    const payload = { notification: { title: title, body: body } };
    return admin.messaging().sendToDevice(token, payload);
};

I think lint should report all of the foregoing as just fine, even with promise nesting warning turned on.

Upvotes: 2

Ben
Ben

Reputation: 138

Thanks to danh, here's my final code. Comment/feedback away! I decided to disable the promise nesting option within lint and viola!

exports.newSessionNotifer = functions.database.ref('/sessions/{sessionID}/college').onCreate((snap, context) => {
const college = snap.val(); 
return admin.database().ref('colleges').child(college).once('value').then((snapshot) => {
    const people = snapshot.val();  
    let advisors = Object.keys(people);
    return advisors;
}).then((advisors) => {
    return advisors.map(advisor => {
            return admin.database().ref('users').child(advisor).child('fcmtoken').child('token').once('value').then((snapshot) => {
            const token = snapshot.val();
            const payload = {
                    notification: {
                    title: `There's a new session for ${college}!`,
                    body: 'Go to the middle tab and swipe right to accept the session before your peers do!'
                    }
            };
            return admin.messaging().sendToDevice(token, payload);
        });
    });
});
});

Upvotes: 2

Related Questions