Javascript 如何使用事件和承诺控制程序流?

Javascript 如何使用事件和承诺控制程序流?,javascript,node.js,tcp,promise,ecmascript-6,Javascript,Node.js,Tcp,Promise,Ecmascript 6,我有一门课是这样的: import net from 'net'; import {EventEmitter} from 'events'; import Promise from 'bluebird'; class MyClass extends EventEmitter { constructor(host = 'localhost', port = 10011) { super(EventEmitter); this.host = host;

我有一门课是这样的:

import net from 'net';
import {EventEmitter} from 'events';
import Promise from 'bluebird';

class MyClass extends EventEmitter {
    constructor(host = 'localhost', port = 10011) {
        super(EventEmitter);
        this.host = host;
        this.port = port;
        this.socket = null;
        this.connect();
    }
    connect() {
        this.socket = net.connect(this.port, this.host);
        this.socket.on('connect', this.handle.bind(this));
    }
    handle(data) {
        this.socket.on('data', data => {

        });
    }
    send(data) {
        this.socket.write(data);
    }
}
如何将
send
方法转换为承诺,从套接字的
data
事件返回值?服务器仅在向其发送数据时才发回数据,而不是可以轻松抑制的连接消息

我试过这样的方法:

handle(data) {
    this.socket.on('data', data => {
        return this.socket.resolve(data);
    });
    this.socket.on('error', this.socket.reject.bind(this));
}
send(data) {
    return new Promise((resolve, reject) => {
        this.socket.resolve = resolve;
        this.socket.reject = reject;
        this.socket.write(data);
    });
}
显然,这不起作用,因为当链接和/或并行多次调用
send
时,
resolve
/
reject
将相互覆盖

还有一个问题是并行调用
send
两次,然后它解决了哪个响应先返回的问题

我目前有一个使用队列和延迟的实现,但它感觉很混乱,因为队列一直在被检查

我希望能够做到以下几点:

let c = new MyClass('localhost', 10011);
c.send('foo').then(response => {
    return c.send('bar', response.param);
    //`response` should be the data returned from `this.socket.on('data')`.
}).then(response => {
    console.log(response);
}).catch(error => console.log(error));
只是补充一下,我对接收到的数据没有任何控制权,这意味着它不能在流之外修改

编辑:这似乎是不可能的,因为TCP没有请求-响应流。如何仍然使用承诺来实现这一点,而不是使用单个执行(一次一个请求)承诺链或队列。

如果您的
send()
调用相互干扰,您应该将其保存到缓存中。为了确保收到的消息与发送的消息匹配,您应该为每条消息分配一些唯一的
id
,并将其分配到有效负载中

因此,您的邮件发件人将如下所示

class MyClass extends EventEmitter {
  constructor() {
    // [redacted]
    this.messages = new Map();
  }

  handle(data) {
    this.socket.on('data', data => {
       this.messages.get(data.id)(data);
       this.messages.delete(data.id);
    });
  }

  send(data) {
    return return new Promise((resolve, reject) => {
        this.messages.set(data.id, resolve);
        this.socket.write(data);
    });
  }
}
此代码对消息顺序不敏感,您将获得所需的API。

socket.write(数据[,编码][,回调])
接受回调。您可以在此回调中拒绝或解析

class MyClass extends EventEmitter {
  constructor(host = 'localhost', port = 10011) {
    super(EventEmitter);
    this.host = host;
    this.port = port;
    this.socket = null;
    this.requests = null;
    this.connect();
  }
  connect() {
    this.socket = net.connect(this.port, this.host);
    this.socket.on('connect', () => {
      this.requests = [];
      this.socket.on('data', this.handle.bind(this));
      this.socket.on('error', this.error.bind(this));
    });
  }
  handle(data) {
    var [request, resolve, reject] = this.requests.pop();
    // I'm not sure what will happen with the destructuring if requests is empty
    if(resolve) {
      resolve(data);
    }
  }
  error(error) {
    var [request, resolve, reject] = this.requests.pop();
    if(reject) {
      reject(error);
    }
  }
  send(data) {
    return new Promise((resolve, reject) => {
      if(this.requests === null) {
        return reject('Not connected');
      }
      this.requests.push([data, resolve, reject]);
      this.socket.write(data);
    });
  }
}
未测试,因此不确定方法签名,但这是基本思想

这假设每个请求将有一个
句柄
错误
事件

我想得越多,如果没有应用程序数据中的附加信息(如数据包编号)来匹配请求的响应,这似乎是不可能的


现在它的实现方式(以及您的问题中的实现方式)甚至不确定一个答案是否与一个
句柄
事件完全匹配。

我将问题提取到最低限度,并使其在浏览器中运行:

  • 套接字类被模拟
  • 已从
    EventEmitter
    中删除有关端口、主机和继承的信息
  • 该解决方案的工作原理是将新请求附加到承诺链,但允许在任何给定时间点最多有一个打开/未响应的请求
    .send
    每次调用时都返回一个新的承诺,并且该类负责所有内部同步。因此,
    .send
    可以被多次调用,并保证请求处理的正确顺序(FIFO)。我添加的另一个特性是修剪承诺链,如果没有未决的请求


    警告我完全忽略了错误处理,但无论如何它都应该根据您的特定用例进行定制



    你是说像双向聊天?发送一条消息,然后等待直到收到一条消息,就像这样?@thefourtheye差不多,除了我可能需要并行调用
    Send
    ,承诺应该根据发送的内容返回正确的响应。虽然所有接收到的数据都来自一个流,所以它不是完全可追踪的。我在这里猜测。。。您是否可以在
    socket
    中使用
    .add()
    方法设置某种观察者对象,然后在您描述的方法(您尝试过的方法)中调用
    this.socket.observer.add({reject:reject,resolve:resolve};
    from
    send()
    ?您的意思是like Q-Connection?),链接时覆盖不是问题,因为您只在
    then
    处理程序中第二次调用
    send
    (即,在解决第一个承诺之后).关于并行
    send
    s,无论语言/代码结构如何,这都是不可能的,因为问题在协议定义中。如果需要串行请求/响应通信协议(即,没有相关消息id),在发送下一个请求之前,您必须遵守规则并等待响应。无法在脚本外部分配
    id
    。流不会返回
    id
    ,这意味着脚本将不知道收到了哪个响应。因此,您想用套接字的下一个数据帧来解析承诺吗?此解决方案ion看起来不可靠,但你可以这样做,如果你使用
    messages
    数组而不是object,那么回调只是为了检查数据是否已发送,我想在收到数据时解析承诺。唯一收到的数据来自
    data
    事件。那么你的承诺就不太幸运了。关键是只运行一次。t他承诺不管它已经发生还是将来可能发生都要这样做……但是承诺只能解决一次。基本上我希望流程是
    send->create Promise->return Promise->receive data->resolve Promise
    。是的,就像我说的,它只解决一次,这是承诺的重点。您的用例用承诺是不好的。
    class SocketMock {
    
      constructor(){
        this.connected = new Promise( (resolve, reject) => setTimeout(resolve,200) ); 
        this.listeners = {
      //  'error' : [],
        'data' : []
        }
      }
    
      send(data){
    
        console.log(`SENDING DATA: ${data}`);
        var response = `SERVER RESPONSE TO: ${data}`;
        setTimeout( () => this.listeners['data'].forEach(cb => cb(response)),               
                   Math.random()*2000 + 250); 
      }
    
      on(event, callback){
        this.listeners[event].push(callback); 
      }
    
    }
    
    class SingleRequestCoordinator {
    
        constructor() {
            this._openRequests = 0; 
            this.socket = new SocketMock();
            this._promiseChain = this.socket
                .connected.then( () => console.log('SOCKET CONNECTED'));
          this.socket.on('data', (data) => {
            this._openRequests -= 1;
            console.log(this._openRequests);
            if(this._openRequests === 0){
              console.log('NO PENDING REQUEST --- trimming the chain');
              this._promiseChain = this.socket.connected
            }
            this._deferred.resolve(data);
          });
    
        }
    
        send(data) {
          this._openRequests += 1;
          this._promiseChain = this._promiseChain
            .then(() => {
                this._deferred = Promise.defer();
                this.socket.send(data);
                return this._deferred.promise;
            });
          return this._promiseChain;
        }
    }
    
    var sender = new SingleRequestCoordinator();
    
    sender.send('data-1').then(data => console.log(`GOT DATA FROM SERVER --- ${data}`));
    sender.send('data-2').then(data => console.log(`GOT DATA FROM SERVER --- ${data}`));
    sender.send('data-3').then(data => console.log(`GOT DATA FROM SERVER --- ${data}`));
    
    setTimeout(() => sender.send('data-4')
        .then(data => console.log(`GOT DATA FROM SERVER --- ${data}`)), 10000);