如何让 JavaScript 执行等待 socket.on 函数响应?

1 javascript jquery websocket node.js socket.io

var data = bar();

function bar() {
  var result;

  socket.emit('message', 'test');
  socket.on('msg', function(message) {
    result = message;
  });

  return result;
}
Run Code Online (Sandbox Code Playgroud)

在这里,结果值是undefined。如何将从socket.on函数中检索到的值分配给result变量并返回?

这是我接收socket.io消息事件的另一端。

var io = require('socket.io').listen(server);

io.sockets.on('connection', function(socket, username) {
  socket.on('message', function(message) {
    var result = 'test value'
    socket.emit('msg', result);
  });
});
Run Code Online (Sandbox Code Playgroud)

试图在确认中返回一个对象。

io.sockets.on('connection', function (socket) {
    socket.on('message', function(message, ackCallback) {
        console.log("server received message", message);
        var result = tools.execute(message);  
        console.log(typeof(result)); // result is returned a object
        ackCallback(result);    
    });  
});
Run Code Online (Sandbox Code Playgroud)

PS我试图传递一个简单的对象

var result = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};
ackCallback(result);
Run Code Online (Sandbox Code Playgroud)

正在为此对象传递结果值。


工具。执行(消息);正在返回这个对象。哪个没有通过 ackCallback 传递。

{ uuid: '155C75EA-CB23-4172-85EB-3E256A271D8D', name: '', type: 'Object3D', parent: null, children: [ { uuid: 'D778A19B-F935-4B06-8594BC5F name: ' '', type: 'Mesh', parent: [Circular], children: [], up: [Object], position: [Object], rotation: [Object], 四元数: [Object], scale: [Object],矩阵:[Object],matrixWorld:[Object],matrixAutoUpdate:true,matrixWorldNeedsUpdate:false,layers:[Object],可见:true,castShadow:false,receiveShadow:false,frustumCulled:true,renderOrder:0,userData:{} ,几何:[对象],材料:[对象],drawMode:0},{uuid:'9FD1CACF-6A2B-4932-8FA2-B0A4AF618F8D',名称:'',类型:'Mesh',父:[Circular],子:[],向上:[Object],位置:[Object],旋转:[Object],四元数:[Object],比例:[Object] ], matrix: [Object], matrixWorld: [Object], matrixAutoUpdate: true, matrixWorldNeedsUpdate: false, layers: [Object], visible: true, castShadow: true, receiveShadow: false, frustumCulled: true, renderOrder: 0, userData: {},几何:[物体],材质:[物体],drawMode:0}],向上:{x:0,y:1,z:0},位置:{x:0,y:0,z: 0 }, 旋转: { _x: 0, _y: 0, _z: 0, _order: 'XYZ', onChangeCallback: [Function: onRotationChange] }, 四元数: { _x: 0, _y: 0, _z: 0, _w:1、onChangeCallback: [Function: onQuaternionChange] }, scale: { x: 1, y: 1, z: 1 }, matrix: { elements: Float32Array [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] }, matrixWorld: { 元素: Float32Array [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 , 0, 0, 1 ] }, matrixAutoUpdate: true, matrixWorldNeedsUpdate: false, layers: { mask: 1 }, visible: true, castShadow: false, receiveShadow: false, frustumCulled: true, renderOrder: 0, userData: {} }castShadow: false, receiveShadow: false, frustumCulled: true, renderOrder: 0, userData: {} }castShadow: false, receiveShadow: false, frustumCulled: true, renderOrder: 0, userData: {} }

jfr*_*d00 8

您不能“让 Javascript 等待”异步事件发生。这不是 Javascript 的工作方式。它是一种事件驱动的架构,根本就不能那样工作。

另外,当通过某种异步操作获得该值时,无法直接从函数返回该值。该函数将在值准备好之前很久就返回。您可以在有关该一般主题的规范答案中查看有关原因的详细信息以及此处的一些选项。

处理异步结果的现代方法是返回一个承诺,该承诺将在您的事件发生时得到解决,然后调用者可以使用该承诺来获得最终结果。

function bar(data, timeout = 10000) {
    return new Promise((resolve, reject) => {
        let timer;

        socket.emit('message', data);

        function responseHandler(message) {
            // resolve promise with the value we got
            resolve(message);
            clearTimeout(timer);
        }

        socket.once('msg', responseHandler); 

        // set timeout so if a response is not received within a 
        // reasonable amount of time, the promise will reject
        timer = setTimeout(() => {
            reject(new Error("timeout waiting for msg"));
            socket.removeListener('msg', responseHandler);
        }, timeout);

    });
}

bar().then(message => {
   // you can use message here and only in here
});
Run Code Online (Sandbox Code Playgroud)

我在这里展示的是一个更高级的版本,它也实现了超时,所以如果在一段时间内没有收到响应,承诺将被拒绝。这是为了避免创建一个可能永远挂起的承诺,永远不会得到回应,因此永远不会解决。


注意:如果您正在使用 socket.io 实现某种请求/响应,您可以从您的第一条消息中获得直接响应,这是使用 socket.io 响应特定请求的更好方法。你可以在 doc 中看到ack回调选项是如何工作socket.emit() 。要使用它,您需要连接两端的配合。您发送消息并指定ack回调。消息的接收者然后提供一些ack数据,您的ack回调将收到。这将是对您的消息的直接响应,如果当时还有其他对同一消息的请求,则它不会混淆。对于 socket.io 中的请求/响应,这是首选架构。为了向您展示如何实现它的完整代码示例,我们必须查看接收 socket.iomessage事件的代码的另一端。


好的,现在您已经显示了连接的另一端,您可能应该使用acksocket.io的响应功能:

function bar(timeout = 10000) {
    return new Promise((resolve, reject) => {
        console.log("client sending message 'test');
        socket.emit('message', 'test', function(response) {
            console.log("client got ack response", response);
            resolve(response);
        });
    });
}

bar().then(message => {
   // you can use message here and only in here
   console.log("response from bar(): ", message);
});
Run Code Online (Sandbox Code Playgroud)

服务器端:

var io = require('socket.io').listen(server);

io.sockets.on('connection', function (socket) {
    socket.on('message', function(message, ackCallback) {
        console.log("server received message", message);
        var result = 'test value'
        console.log("server sending back result", result);
        ackCallback(result);    
    });  
});
Run Code Online (Sandbox Code Playgroud)

PS 您的服务器端代码username在此处显示了一个参数:

io.sockets.on('connection', function(socket, username) {
Run Code Online (Sandbox Code Playgroud)

没有username传递给该事件处理程序的这样的参数,所以我不确定你从哪里得到的。你的代码没有显示实际使用它,所以我只是删除了它(因为它无论如何都是无效的)。