【问题标题】:Each then() should return a value or throw每个 then() 应该返回一个值或抛出
【发布时间】:2020-04-02 00:38:08
【问题描述】:

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

        }
    });
});

【问题讨论】:

  • 你应该扁平化你的承诺链。 if(i === length){ 是你可以用老式的回调地狱做的事情,但是你应该用 promises 链接它们并在另一个 then 回调中执行最终逻辑。至于错误:当if(i === length){ 不正确时,您没有return 发生。
  • 如果您对该消息进行网络搜索,您会发现很多关于它的讨论。这是一个 eslint 警告,它表明您可能在代码中做错了什么。你应该注意它给你的行号,这样你就可以弄清楚它到底在哪里引发了问题。
  • 顺便说一句,你是 returning 从你的循环的第一次迭代开始,所以没有循环发生。

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


【解决方案1】:

该消息是由 eslint 检测到您有一个 then() 处理程序引起的,该处理程序可以在不返回值或引发错误的情况下完成。

这是由您在下一行中的 for 循环引起的,因为如果 data 为空,您没有返回值或抛出:

for (var user_id in data) {

正如其他人评论的那样,您的 for 循环将无法正确执行,因为您正在返回一个承诺并仅在第一次迭代时完成处理程序。

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());

此外,发送添加或删除用户的通知/消息可以使用以下定义的 Cloud Functions 轻松实现:

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

问题 cmets 中还建议扁平化您的承诺链,由于有效地完成任务所需的更改数量,我决定只制作它们并注意代码本身的每个更改。下面的代码依赖于上面讨论的聊天室成员列表的重组。

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)
        });
});

【讨论】:

  • 谁能给我一个代码来解决来自 for 循环的问题 for (var user_id in data) { // ... return reference.once('value').then (snap => { // ... }
  • @AyomideAjayi 正如我在这个答案中所提到的,问题不仅仅是代码中的单行语法错误,而是一系列问题。如果您更改存储聊天室成员的方式,最后一个代码块就是工作代码。如果您不想更改,只需将Object.keys(snap.val()) 更改为snap.val(),代码将继续工作。
猜你喜欢
  • 1970-01-01
  • 2019-07-10
  • 2018-09-11
  • 1970-01-01
  • 1970-01-01
  • 2020-07-24
  • 1970-01-01
  • 1970-01-01
  • 2019-02-19
相关资源
最近更新 更多