[英]firebase function, counter takes too long to update
I have implemented a counter variable in my real time database, the users are able to call the function and upon 4 calls it should run some code.我已经在我的实时数据库中实现了一个计数器变量,用户可以调用 function 并且在 4 次调用时它应该运行一些代码。 The problem is that it may take too long to run and update the counter, so if the counter = 1 and two people call the function at a relative same time both of them will get问题是运行和更新计数器可能需要很长时间,所以如果计数器 = 1 并且两个人同时调用 function 他们两个都会得到
datasnapshot.child("counter").val() = 1
and both of them will set他们都将设置
promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1))) = 2
while it should be 3虽然应该是 3
how can I fix this?我怎样才能解决这个问题? (I guess by either making the update faster or forcing the function to wait for the previous one to end (not sure if possible), or something else that I am missing I am new to firebase & ts) (我想要么加快更新速度,要么强制 function 等待前一个结束(不确定是否可能),或者我错过的其他东西我是 firebase 和 ts 的新手)
this is my code:这是我的代码:
exports.functionTST = functions.https.onCall(async (data, context) => {
const promises = [];
JSON.parse(JSON.stringify(data), (key, value) => {
parse values
});
//set values
promises.push(admin.database().ref('game/' + matchkey + '/'...).update({
val1: val2,
}))
//counter
admin.database().ref('game/' + matchkey).once('value')
.then(datasnapshot => {
let counter = datasnapshot.child("counter").val()
if (counter === null) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').set(1))
} else if (counter < 3) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1)))
} else if (counter == 3) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').remove())
//do other stuff
}
});
Promise.all(promises);
return null
}
});
Thanks !!谢谢 !!
First, let's fix your indentation.首先,让我们修复您的缩进。
exports.functionTST = functions.https.onCall(async (data, context) => {
const promises = [];
JSON.parse(JSON.stringify(data), (key, value) => {
// parse values
});
// set values
promises.push(
admin.database()
.ref('game/' + matchkey + '/'...)
.update({
val1: val2,
})
);
// counter
admin.database()
.ref('game/' + matchkey)
.once('value')
.then(datasnapshot => {
let counter = datasnapshot.child("counter").val()
if (counter === null) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').set(1))
} else if (counter < 3) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1)))
} else if (counter == 3) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').remove())
// do other stuff
}
});
Promise.all(promises);
return null
});
From this, we can now see a few issues.从这里,我们现在可以看到一些问题。
The most notable error, is that you aren't awaiting or returning the Promise
chain properly.最值得注意的错误是您没有正确等待或返回Promise
链。 When you don't wait for the promises to resolve, your function will get severely throttled and network requests (such as updating the database) can be skipped entirely.如果您不等待承诺解决,您的 function 将受到严重限制,并且可以完全跳过网络请求(例如更新数据库)。
Promises.all(promises)
return null; // return no data
should be应该
await Promises.all(promises) // <-- note addition of await here
return null; // return no data
or或者
return Promises.all(promises) // <-- note moving return here
.then(() => null) // return no data
This next block of code does nothing useful.下一个代码块没有任何用处。 Were you meant to store the result of JSON.parse
into a new variable?您是否打算将JSON.parse
的结果存储到一个新变量中? Depending on what you are trying to achieve Object.entries(data)
might be a better choice.根据您要实现的目标Object.entries(data)
可能是更好的选择。
JSON.parse(JSON.stringify(data), (key, value) => {
// parse values
});
This next block of code spawns a floating Promise
- you aren't returning it, nor are you storing it into an array like promises
- so it will cause inconsistent behaviour (like never updating the counter).下一个代码块产生一个浮动的Promise
- 你没有返回它,也没有将它存储到一个类似promises
的数组中 - 所以它会导致不一致的行为(比如从不更新计数器)。
admin.database()
.ref('game/' + matchkey)
.once('value')
.then(/* ... */)
The entirety of the code shown in this block should be replaced by a transaction operation instead to streamline the database updates and ensure that it is properly updated.此块中显示的全部代码应替换为事务操作,以简化数据库更新并确保正确更新。
// set values
promises.push(
admin.database()
.ref('game/' + matchkey + '/'...)
.update({
val1: val2,
})
);
// counter
admin.database()
.ref('game/' + matchkey)
.once('value')
.then(datasnapshot => {
let counter = datasnapshot.child("counter").val()
if (counter === null) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').set(1))
} else if (counter < 3) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1)))
} else if (counter == 3) {
promises.push(admin.database().ref('game/' + matchkey + '/counter').remove())
//do other stuff
}
});
Promises.all(promises)
return null;
Adapting the transaction example and merging your other logic gives:调整事务示例并合并您的其他逻辑给出:
//counter
await admin.database() // <-- note addition of await here
.ref('game/' + matchkey)
.transaction((matchInfo) => {
if (matchInfo) {
// set updated values
matchInfo[/* The '...' from .ref('game/' + matchkey + '/'...) */].val1 = val2;
// e.g.
// matchInfo.round1.val1 = val2;
// OR
// matchInfo.round1 = Object.assign(
// matchInfo.round1 || {}, // <-- creates the property when it is missing
// {
// val1: val2
// }
// );
// handle & update counter
const counter = matchInfo.counter || 0;
if (counter < 3) {
matchInfo.counter = counter + 1;
} else {
// clear counter and do other stuff
delete matchInfo.counter;
// e.g.
// matchInfo.winner = matchInfo.players[0]
}
}
return matchInfo; // apply changes
});
return null; // return no data to the caller
After implementing the above block, your function can become something similar to:实现上述块后,您的 function 可以变成类似于:
exports.functionTST = functions.https.onCall(async (data, context) => {
// parse request body
const matchkey = data.matchkey;
/* ... */
// update match information
await admin.database() // <-- note addition of await here
.ref('game/' + matchkey)
.transaction((matchInfo) => {
/* ... */
})
return null;
To ensure that the function returns appropriate errors, you should surround each part in try-catch blocks and throw an appropriate HttpsError
so failures can be handled gracefully by the caller on the client side:为确保 function 返回适当的错误,您应该围绕 try-catch 块中的每个部分并抛出适当的HttpsError
以便客户端的调用者可以优雅地处理故障:
exports.functionTST = functions.https.onCall(async (data, context) => {
// parse request body
try {
const matchkey = data.matchkey;
/* ... */
} catch (err) {
console.log('Unexpected error while handling request data: ', err);
throw new functions.https.HttpsError('invalid-argument', 'Could not parse request data');
}
// update match information
try {
await admin.database() // <-- note addition of await here
.ref('game/' + matchkey)
.transaction((matchInfo) => {
/* ... */
})
} catch (err) {
console.log('Unexpected error while updating match information: ', err);
throw new functions.https.HttpsError('internal', 'Could not update match information');
}
return null; // return no data to the caller
}
First run after deploy take time because the cold start happening, And consider using transaction to prevent race condition.部署后首次运行需要时间,因为冷启动发生,并考虑使用事务来防止竞争条件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.