You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于WebSocket实现Python与JavaScript双向函数调用及异步返回值undefined问题解决方案咨询

解决WebSocket双向函数调用的异步返回值问题

嘿,我帮你捋清楚这个问题!你遇到的核心问题其实是异步编程的思维误区——WebSocket的消息收发都是异步触发的,你的exec_py函数刚发送完消息就直接返回response,这时候onmessage回调还没执行,自然拿不到预期的返回值,结果只能是undefined。

要实现双向函数调用并获取返回值,我们需要用Promise封装异步操作,再配合唯一消息ID来匹配请求和响应(避免多个请求的响应混乱)。下面是具体的实现方案:

一、JavaScript端改进实现

我们把exec_py改成返回Promise,用一个Map存储等待响应的请求,通过消息ID来关联请求和返回结果:

const socket = new WebSocket('ws://127.0.0.1:8765');
const pendingRequests = new Map(); // 存储待响应的请求:key=消息ID,value=Promise的resolve函数
let nextRequestId = 1;

socket.onopen = () => console.log("Server connected");

// 统一处理所有收到的WebSocket消息
socket.onmessage = (event) => {
    const data = JSON.parse(event.data);
    
    // 情况1:收到Python返回的函数执行结果
    if (data.id && pendingRequests.has(data.id)) {
        pendingRequests.get(data.id)(data.result); // 触发Promise的resolve
        pendingRequests.delete(data.id);
    }
    // 情况2:收到Python调用JS函数的请求
    else if (data.callFunction) {
        const { id, callFunction, args = [] } = data;
        // 执行JS中对应的函数(假设函数挂载在window上)
        if (typeof window[callFunction] === 'function') {
            const result = window[callFunction](...args);
            // 把结果带ID返回给Python
            socket.send(JSON.stringify({ id, result }));
        }
    }
};

// 封装调用Python函数的方法,返回Promise
async function exec_py(functionName, args = []) {
    return new Promise((resolve) => {
        const requestId = nextRequestId++;
        pendingRequests.set(requestId, resolve);
        // 发送带ID的请求,告诉Python要执行的函数和参数
        socket.send(JSON.stringify({
            id: requestId,
            callFunction: functionName,
            args
        }));
    });
}

// 使用示例:通过await获取返回值
async function testCallPython() {
    const result = await exec_py('say_hello', ['Mohamed']);
    console.log('Python返回结果:', result); // 输出类似 "Hello from Python, Mohamed!"
}

// 示例JS函数,供Python调用
window.js_greet = (name) => `Hello from JS, ${name}!`;

testCallPython();

二、Python后端对应实现

这里用websockets库做WebSocket服务,同样通过消息ID来匹配请求和响应,支持调用JS函数并获取返回值:

import asyncio
import websockets
import json
from typing import Dict, AsyncioFuture

# 存储待响应的请求:key=消息ID,value=异步Future对象
pending_requests: Dict[int, AsyncioFuture] = {}
next_request_id = 1

async def handle_client(websocket):
    global next_request_id
    async for raw_message in websocket:
        message = json.loads(raw_message)
        
        # 情况1:收到JS调用Python函数的请求
        if message.get('callFunction'):
            req_id = message['id']
            func_name = message['callFunction']
            args = message.get('args', [])
            
            # 执行Python中对应的函数
            if func_name in globals() and callable(globals()[func_name]):
                result = globals()[func_name](*args)
                # 返回结果带原请求ID
                await websocket.send(json.dumps({
                    'id': req_id,
                    'result': result
                }))
        
        # 情况2:收到JS返回的函数执行结果
        elif message.get('id') in pending_requests:
            req_id = message['id']
            pending_requests[req_id].set_result(message['result'])
            del pending_requests[req_id]

# 示例Python函数,供JS调用
def say_hello(name):
    return f"Hello from Python, {name}!"

# 示例:Python主动调用JS函数
async def call_js_function(websocket, func_name, args=[]):
    global next_request_id
    req_id = next_request_id
    next_request_id += 1
    # 创建Future对象等待响应
    future = asyncio.get_event_loop().create_future()
    pending_requests[req_id] = future
    # 发送调用请求给JS
    await websocket.send(json.dumps({
        'id': req_id,
        'callFunction': func_name,
        'args': args
    }))
    # 等待JS返回结果
    result = await future
    print(f"JS返回结果: {result}")
    return result

# 启动WebSocket服务
start_server = websockets.serve(handle_client, "127.0.0.1", 8765)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

三、关键逻辑说明

  1. Promise封装异步操作:exec_py返回Promise,通过await可以“同步”获取异步返回的结果,符合你期望的代码逻辑。
  2. 消息ID匹配机制:每个请求都有唯一ID,响应时带回该ID,确保每个请求都能匹配到自己的返回结果,不会出现多个请求混淆的情况。
  3. 双向调用对称逻辑:不管是JS调Python还是Python调JS,都遵循“发送带ID的请求 → 等待对应ID的响应”的逻辑,实现双向的函数调用和结果返回。

四、替代方案

如果觉得原生WebSocket的异步处理太繁琐,可以考虑这些简化方案:

  • 使用Socket.IO:它封装了WebSocket,提供了更易用的事件回调和请求-响应模式,内置了消息ID匹配,能大幅减少重复代码。
  • HTTP长轮询:如果实时性要求不高,可以用普通HTTP请求,后端挂起请求直到有结果返回,前端用Promise等待响应,但实时性不如WebSocket。
  • RPC框架:比如用FastAPI结合websockets实现RPC,或者使用现成的Python-JS RPC库,直接封装函数调用逻辑,不用自己处理消息格式。

内容的提问来源于stack exchange,提问作者Mohamed Ashraf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:37:34