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};
fromsend()
?您的意思是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);