每个then()应该返回一个值或抛出

时间:2019-12-08 21:04:16

标签: javascript node.js firebase firebase-realtime-database google-cloud-functions

我正在尝试使用群聊系统的云功能发送推送通知,但我在终端中始终收到此错误:每个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);
                          });
                }
            });

        }
    });
});

1 个答案:

答案 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 => {
    // ...
}

Firebase RTDB中的数组

根据您的代码,您在使用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)
        });
});