我正在尝试使用群聊系统的云功能发送推送通知,但我在终端中始终收到此错误:每个then()应该返回值或抛出
为什么会这样?
这是我的代码:
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/chatrooms/{chatroomId}/chatroom_messages/{chatmessageId}')
.onWrite((snap, context) => {
console.log("System: starting");
console.log("snapshot: ", snap);
console.log("snapshot.after: ", snap.after);
console.log("snapshot.after.val(): ", snap.after.val());
//get the message that was written
let message = snap.after.val().message;
let messageUserId = snap.after.val().user_id;
console.log("message: ", message);
console.log("user_id: ", messageUserId);
//get the chatroom id
let chatroomId = context.params.chatroomId;
console.log("chatroom_id: ", chatroomId);
return snap.after.ref.parent.parent.once('value').then(snap => {
let data = snap.child('users').val();
console.log("data: ", data);
//get the number of users in the chatroom
let length = 0;
for(value in data){
length++;
}
console.log("data length: ", length);
//loop through each user currently in the chatroom
let tokens = [];
let i = 0;
for(var user_id in data){
console.log("user_id: ", user_id);
//get the token and add it to the array
let reference = admin.database().ref("/users/" + user_id);
return reference.once('value').then(snap => {
//get the token
let token = snap.child('messaging_token').val();
console.log('token: ', token);
tokens.push(token);
i++;
//also check to see if the user_id we're viewing is the user who posted the message
//if it is, then save that name so we can pre-pend it to the message
let messageUserName = "";
if(snap.child('user_id').val() === messageUserId){
messageUserName = snap.child('name').val();
console.log("message user name: " , messageUserName);
message = messageUserName + ": " + message;
}
//Once the last user in the list has been added we can continue
if(i === length){
console.log("Construction the notification message.");
let payload = {
data: {
data_type: "data_type_chat_message",
title: "Tabian Consulting",
message: message,
chatroom_id: chatroomId
}
};
return admin.messaging().sendToDevice(tokens, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
console.log("Successfully sent message:", response);
return response;
})
.catch(function(error) {
console.log("Error sending message:", error);
});
}
});
}
});
});
答案 0 :(得分:0)
该消息是由于检测到您有一个then()
处理程序而完成的,而该处理程序可以完成而无需返回值或引发错误。
它是由下一行中的for循环引起的,因为如果data
为空,则不返回值也不抛出:
for (var user_id in data) {
正如其他人所评论的那样,您的for循环将无法正确执行,因为您仅在第一次迭代时就返回了promise并完成了处理程序。
for (var user_id in data) {
// ...
return reference.once('value').then(snap => {
// ...
}
根据您的代码,您在使用RTDB中的数组时遇到了一些问题,如this blog post中所述。
与其使用数组来跟踪聊天室的成员(如下),不如使用键值对方法。键值对中存储的值可以是简单的true
值;或可以赋予它含义(对于管理员,true
,对于其他人,false
)。
// Array-based list
"chatrooms/chatroomId1": {
"chatroom_messages": { ... },
"users": [
"userId1",
"userId2",
"userId3"
]
}
// RTDB stores above data as:
"chatrooms/chatroomId1": {
"chatroom_messages": { ... },
"users": {
"0": "userId1",
"1": "userId2",
"2": "userId3"
}
}
// Recommeneded: key-value pairs
"chatrooms/chatroomId1": {
"chatroom_messages": { ... },
"users": {
"userId1": true,
"userId2": false,
"userId3": false
}
}
这种方法的主要好处是从房间删除用户更简单,这将有助于清理垃圾邮件用户/消息。要删除用户,您只需致电
firebase.database().ref("chatrooms/chatroomId1/users/userId1").delete();
而不是
firebase.database().ref("chatrooms/chatroomId1/users").orderByValue().equalTo("userId1").once('value')
.then((snap) => snap.delete());
此外,使用以下定义的云功能可以轻松实现发送添加/删除用户的通知/消息:
functions.database.ref('/chatrooms/{chatroomId}/users/{userId}').onCreate(...)
functions.database.ref('/chatrooms/{chatroomId}/users/{userId}').onDelete(...)
在处理异步任务时,请完全避免使用for循环,因为它们容易引起无法检测到的错误,并且现代Javascript提供了更好的替代方法。一种这样的方法是使用Promise.all(someArray.map(value => {...}))
习惯用法covered in this answer。
在问题注释中还建议使用扁平化您的承诺链,由于有效执行任务需要进行大量更改,因此我决定只进行更改并记下代码本身的每个更改。下面的代码依赖于上面讨论的聊天室成员列表的重组。
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(); // CHANGED: Cloud Functions provides the needed environment variables to initialize this for you when called without arguments.
exports.sendNotification = functions.database.ref('/chatrooms/{chatroomId}/chatroom_messages/{chatMessageId}') // CHANGED: renamed 'chatmessageId' to 'chatMessageId' (consistent camelCaseStyling)
.onWrite((change, context) => { // CHANGED: renamed 'snap' to 'change' (matches actual type & less ambiguous below)
if (!change.after.exists()) { // CHANGED: Handle when message was deleted
// message deleted. abort
console.log(`Message #${context.params.chatMessageId} in Room #${context.params.chatroomId} deleted. Aborting.`);
return;
}
let messageData = change.after.val(); // CHANGED: avoid calling change.after.val() multiple times
// console.log("New data written: ", messageData); // CHANGED: Removed verbose log commands.
let message = messageData.message;
let messageAuthorId = messageData.user_id; // CHANGED: renamed 'messageUserId' to 'messageAuthorId' (less ambiguous)
let chatroomId = context.params.chatroomId;
console.log("New message:", { // CHANGED: merged log commands (less StackDriver API overhead when deployed)
user_id: messageAuthorId,
chatroom_id: chatroomId,
message: message
});
let chatroomMembersRef = change.after.ref.parent.parent.child('users'); // CHANGED: only got needed data
return chatroomMembersRef.once('value')
.then(snap => {
// DATABASE STRUCTURE CHANGE: "/chatrooms/{chatroomId}/users" - change array (["userId1", "userId2", "userId3"]) to a userId keyed OBJECT (e.g. {"userId1": true, "userId2": true, "userId3": true})
let chatroomMemberList = Object.keys(snap.val()); // CHANGED: renamed 'data' to 'chatroomMemberList' (less ambiguous)
// console.log("Chatroom Members: ", {
// count: chatroomMemberList.length,
// members: chatroomMemberList
// });
// Asyncronously, in parallel, retrieve each member's messaging token
let chatroomMemberTokenPromises = chatroomMemberList.map((memberId) => { // CHANGED: renamed 'user_id' to 'memberId' (less ambiguous, consistent camelCaseStyling)
let memberDataRef = admin.database().ref("/users/" + memberId); // CHANGED: renamed 'reference' to 'memberDataRef' (less ambiguous)
// CHANGED: For each member, get only their registration token (rather than all of their user data)
let getMessagingTokenPromise = memberDataRef.child('messaging_token').once('value').then((memberTokenSnap) => {
console.log("Got messaging token for member #", memberId);
return memberTokenSnap.val();
});
// If this member is the message author, also get their name to prepend to the notification message.
if (memberId === messageAuthorId) {
let prependUserNamePromise = memberDataRef.child('name').once('value')
.then((memberNameSnap) => {
let messageAuthorName = memberNameSnap.val();
console.log("Message author's name: " , messageAuthorName);
message = messageAuthorName + ": " + message;
});
return Promise.all([getMessagingTokenPromise, prependUserNamePromise])
.then(results => results[0]); // only return result of getMessagingTokenPromise
} else {
return getMessagingTokenPromise;
}
});
// Wait for all of the messaging tokens
return Promise.all(chatroomMemberTokenPromises);
})
.then((chatroomMemberTokensArray) => {
console.log("Constructing the notification message...");
let payload = {
data: {
data_type: "data_type_chat_message",
title: "Tabian Consulting",
message: message,
chatroom_id: chatroomId
}
};
return admin.messaging().sendToDevice(chatroomMemberTokensArray, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
console.log("Successfully sent message:", response);
return response;
})
.catch(function(error) {
console.log("Error sending message:", error);
});
})
.catch((error) {
console.log("Unexpected error:", error)
});
});