簡體   English   中英

如何使用事件和承諾來控制程序流?

[英]How can I control program flow using events and promises?

我有一個這樣的課:

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方法轉換為promise,它從套接字的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沒有請求 - 響應流。 如何使用promises實現它,但是使用單次執行(一次一個請求)promise鏈或隊列。

我將問題提煉到最低限度並使瀏覽器可以運行:

  1. 套接字類被嘲笑。
  2. EventEmitter刪除了有關端口,主機和繼承的EventEmitter

該解決方案的工作原理是將新請求附加到promise鏈,但在任何給定時間點允許最多一個打開/未應答請求。 .send每次調用時.send返回一個新的promise,並且該類負責所有內部同步。 因此,可以多次調用.send並保證請求處理的正確有序(FIFO)。 如果沒有待處理請求,我添加的另一個功能是修剪承諾鏈。


警告我完全省略了錯誤處理,但無論如何都應根據您的特定用例進行調整。


DEMO

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

如果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(data[, encoding][, callback])接受回調。 您可以拒絕或解決此回調。

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

沒有測試,因此不確定方法簽名,但這是基本的想法。

這假設每個請求都會有一個handleerror事件。

我想的越多,如果沒有應用程序數據中的其他信息,這似乎是不可能的,例如數據包號碼以匹配對請求的響應。

它現在實現的方式(以及它在你的問題中的方式),它甚至不確定一個答案是否恰好匹配一個handle事件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM