在Flask应用中更新键值对时出现“线程异常”错误

时间:2019-07-26 03:37:56

标签: javascript python flask socket.io flask-socketio

我正在构建一个基本的聊天程序(CS50s网络编程中的Flack)。

我有一本字典,其中将通道和消息存储为键值对。

消息在列表中,因此一对键值看起来像:

{"channelExample" : ["msg1", "msg2"]}

我还有一个名为 currentRoom 的变量,用于跟踪用户正在发送消息的当前房间/通道。

当用户提交消息时,我正在尝试通过执行以下操作来更新该频道中的消息( emit 已导入,并且我已经确认 currentRoom 和输入的消息是字符串值):

@socketio.on("submit message")
def submitMessage(message):
    channels[currentRoom].append(message)
    emit("display message", message)

但是我为channels[currentRoom].append(message)被抛出“ 线程异常... ”错误,我不确定为什么。

我在Flask中的完整代码:

import os

from flask import Flask, session, render_template, url_for, request, flash, redirect, jsonify
from flask_socketio import SocketIO, send, emit, join_room, leave_room


app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
socketio = SocketIO(app)

currentRoom = None
channels = {}

@app.route("/")
def index():
    return render_template("welcome.html", channels=channels)

@socketio.on("new channel")
def newChannel(channelName):
    # Store new channel to keep track of it
    channels.update( {channelName : []} )

@socketio.on("retrieve channels") 
def retrieveChannels():
    channelNames = []

    for channel in channels:
        channelNames.append(channel)

        emit("providing channels", channelNames)

@socketio.on("retrieve messages")    
def loadMessages(channelName):
    currentRoom = channelName

    channelMessages = channels[currentRoom]

    emit("load messages", channelMessages)

@socketio.on("submit message")
def submitMessage(message):
    channels[currentRoom].append(message)
    emit("display message", message)

Javascript:

document.addEventListener('DOMContentLoaded', () => {

    // Connect to websocket
    var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);

    // When connected, 
    socket.on('connect', () => {

        var nameInput = document.querySelector("#usernameInput");
        var welcomeMessage = document.querySelector("#welcomeMessage");
        var createChannel = document.querySelector("#createChannel");
        var newChannelForm = document.querySelector("#newChannelForm");
        var newMessageForm = document.querySelector("#newMessageForm");

        function userExists() {
            // Check if user has come here before
            if (localStorage.getItem("username")) {
                // Display a welcome message
                welcomeMessage.innerHTML = `Welcome back ${localStorage.getItem("username")}!`;
                nameInput.style.display = "none";
                return true;
            }
            else {
                return false;
            }
        };

        function createChannelBtn(name) {
            // Create new channel & style it
            let newChannel = document.createElement("button");
            newChannel.id = name;
            newChannel.innerHTML = name;
            newChannel.className = "btn btn-block btn-outline-dark";
            newChannel.style.display = "block";

            // Attach to current list 
            document.querySelector("#channels").appendChild(newChannel);

            // When someone clicks the channel
            newChannel.onclick = () => {

                newChannel.classList.toggle("active");

                socket.emit("retrieve messages", newChannel.id);
                console.log("Retrieving messages!!!");

                socket.on("load messages", channelMessages => {
                    console.log("loading messages!");
                    for (let i = 0; i < channelMessages.length; i++) {
                        createMessage(channelMessages[i]);
                    }
                });

            };
        };

        function createMessage(messageContent) {
            let message = document.createElement("h6");
            message.innerHTML = messageContent;
            document.querySelector("#messageWindow").appendChild(message);
            console.log("Currently creating message!");
        };

        function loadChannels() {
            socket.emit("retrieve channels")

            socket.on("providing channels", channelNames => {
                for (let i = 0; i < channelNames.length; i++) {
                    createChannelBtn(channelNames[i]);
                }
            });
        };

        // Make sure the new channel form is not displayed until "Create channel" button is clicked
        newChannelForm.style.display = "none";

        // Check if user exists already in local storage
        userExists();

        loadChannels();

        // If someone submits a username...
        nameInput.addEventListener("click", () => {
            // if that username exists, do nothing
            if (userExists()) {
            }
            // else remember the username
            else {
                localStorage.setItem("username", document.querySelector("#user").value);
            }
        });

        // When someone wants to create a channel
        createChannel.addEventListener("click", () => {
            // Show form
            newChannelForm.style.display = "block";

            // When user inputs new channel name...
            newChannelForm.onsubmit = () => {

                // Retrieve their input
                var newChannelName = document.querySelector("#newChannel").value;

                // Create a new channel
                createChannelBtn(newChannelName);

                // Notify server to store new channel 
                socket.emit("new channel", newChannelName);

                // Clear input field
                document.querySelector("#newChannel").innerHTML = "";

                return false;

            };
        });

        newMessageForm.onsubmit = () => {
            let message = document.querySelector("#newMessage").value;
            console.log("You have entered " + message);

            socket.emit("submit message", message);
            console.log("Submitted message!");

            socket.on("display message", message => {
                createMessage(message);
                console.log("Displaying message!!");
            });

            return false;
        };

    });

    // DOM Ending Bracket
});

1 个答案:

答案 0 :(得分:0)

下次,发布完整的追溯以及所有必要的文件,因为它将使调试代码更加容易。您说您已验证currentRoom是一个字符串值,但可能仅在从浏览器接收到时才验证。您无需在submitMessage()函数中进行检查,然后尝试使用None访问频道列表,如回溯所示,如下所示:

Exception in thread Thread-13:
Traceback (most recent call last):
  File "C:\Users\Michael\AppData\Local\Programs\Python\Python37-32\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "C:\Users\Michael\AppData\Local\Programs\Python\Python37-32\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\socketio\server.py", line 640, in _handle_event_internal
    r = server._trigger_event(data[0], namespace, sid, *data[1:])
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\socketio\server.py", line 669, in _trigger_event
    return self.handlers[namespace][event](*args)
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\flask_socketio\__init__.py", line 280, in _handler
    *args)
  File "c:\Users\Michael\Desktop\sandbox\sandbox\lib\site-packages\flask_socketio\__init__.py", line 694, in _handle_event
    ret = handler(*args)
  File "c:\Users\Michael\Desktop\sandbox\sandbox.py", line 47, in submitMessage
    channels[currentRoom].append(message)
KeyError: None

这应该提醒您,您主要关心的是KeyError,这是因为您要将None传递给channels字典,这意味着currentRoom必须为空。

那为什么会发生呢?问题出在第34行:

32  @socketio.on("retrieve messages")    
33  def loadMessages(channelName):
34      currentRoom = channelName  # <--- Issue here
35  
36      channelMessages = channels[currentRoom]
37  
38      emit("load messages", channelMessages)

Python变量紧跟LEGB rule,这意味着在解析变量名称时,解释器将首先检查 L ocal范围,然后是 E 闭合范围,然后是 G 范围,最后是 B 内置范围。但是,当您在某个范围内分配变量时,Python会在该范围内创建该变量。

因此,您现在已经创建了一个名为currentRoom的新变量,它是与顶部定义的currentRoom分开的 变量,并且仅在loadMessages()功能!因此,当您在函数中进行赋值时,如何让Python知道您正在引用该全局变量?

您使用global关键字:

@socketio.on("retrieve messages")    
def loadMessages(channelName):
    global currentRoom  # <--- This tells Python that `currentRoom` is a global variable
    currentRoom = channelName  # Assign to the global variable

    channelMessages = channels[currentRoom]

    emit("load messages", channelMessages)

现在,当我们调用submitMessage()时,currentRoom现在具有实际的文本值,而不仅仅是None,并且函数成功执行而没有任何错误。