Reputation: 63
New to cloud functions and promises. I've tried adding promises in different locations but still getting the message in logs. First, I'm not sure where I should be adding the promise and second whether I should be returning nothing. I don't need to execute another function after I call match (and create channel if condition met). There will be multiple users though triggering onCreate so I want to make sure things are executed one user at a time.
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp();
//every time user added to liveLooking node
exports.command = functions.database
.ref('liveLooking/{uid}')
.onCreate((snap, context) => {
const uid = context.params.uid
match(uid)
})
function match(uid) {
let m1uid, m2uid
admin.database().ref('liveChannels').transaction((data) => {
//if no existing channels then add user to liveChannels
if (data === null) {
console.log(`${uid} waiting for match`)
return { uid: uid }
}
else {
m1uid = data.uid
m2uid = uid
if (m1uid === m2uid) {
console.log(`$m1uid} tried to match with self!`)
//match user with liveChannel user
} else {
console.log(`matched ${m1uid} with ${m2uid}`)
createChannel(m1uid, m2uid)
return null
}
}
},
(error, committed, snapshot) => {
if (error) {
throw error
}
else {
return {
committed: committed,
snapshot: snapshot
}
}
},
false)
}
function createChannel(uid1, uid2) {
// Add channels for each user matched
const channel_id = uid1+uid2
console.log(`starting channel ${channel_id} with uid1: ${uid1}, uid2: ${uid2}`)
const m_state1 = admin.database().ref(`liveUsers/${uid1}`).set({
channel: channel_id
})
const m_state2 = admin.database().ref(`liveUsers/${uid2}`).set({
channel: channel_id
})
}
Edit 1 - I tried changing the transaction to use await so it will change userLives node only after the transaction. Getting these two warnings. 1) Expected to return a value at the end of async function 'match'. 2) At ***return Arrow function expected a return value. I'm not trying to change anything under LiveChannels if use matches with self. Not sure how to fix that warning. 3) Still getting function return undefined, expected promise or value in the logs - I think for command function.
async function match(uid) {
let m1uid, m2uid;
let createChannel = false
try {
const transactionResult = await admin
.database()
.ref("liveChannels")
.transaction(
(data) => {
if (data === null) {
console.log(`${uid} waiting for match`)
return { uid: uid }
}
else {
m1uid = data.uid
m2uid = uid
if (m1uid === m2uid) {
console.log(`$m1uid} tried to match with self!`)
***return***
} else {
console.log(`matched ${m1uid} with ${m2uid}`)
createChannel = true
return {}
}
}
},
(error, committed, snapshot) => {
if (error) {
throw error
}
else {
return {
committed: committed,
snapshot: snapshot
}
}
},
false
);
if (transactionResult) {
if (createChannel) {
const channel_id = m1uid + m2uid
console.log(`starting channel ${channel_id} with uid1: ${m1uid}, uid2: ${m2uid}`)
const m_state1 = admin.database().ref(`liveUsers/${m1uid}`).set({
channel: channel_id
})
const m_state2 = admin.database().ref(`liveUsers/${m2uid}`).set({
channel: channel_id
})
return Promise.all([m_state1, m_state2])
}
}
} catch (err) {
throw new Error(err);
}
}
Upvotes: 0
Views: 147
Reputation: 83181
You are not correctly managing the life cycle of your Cloud Function. You need to wait that all the asynchronous tasks (i.e. the calls to Firebase asynchronous methods) are completed before indicating to the platform that it can clean up the function. This should be done by returning a Promise, and more precisely, since you chain several asynchronous method calls, by returning the promises chain.
However, there is another problem in your Cloud Function, with the way you write the Transaction
. A Transaction atomically modifies the data at the location (i.e. the Reference
) you call the transaction on.
In your code, you call the transaction on ref('liveChannels')
but inside the transaction, you modify data at other locations: ref(`liveUsers/${uid1}`)
and ref(`liveUsers/${uid2}`)
.
This is not the correct way: If you want to encapsulate all these operations in one Transaction, you need to call the Transaction on a database node which contains all the nodes/locations you want to change, i.e. a common parent/ancestor node of liveChannels
, liveUsers/${uid1}
and liveUsers/${uid2}
.
If I'm not mistaking the only common parent/ancestor node for those three nodes is the DB root node. It is probably not a good idea to call a Transaction on the DB root node, in particular if you have a lot of users using your app concurrently. You should probably adapt your data model (DB structure) to avoid that.
Also note that instead of using the onComplete
callback of the Tansaction to handle its success and failure, you should use the Promise returned by the Transaction, in order to correctly use it in the Promise chain (see first paragraph).
Upvotes: 1