TypeScript WebSocket客户端绑定类方法报错:listener参数无效
问题描述
我是TypeScript新手,想用TypeScript结合websocket库创建WebSocket客户端。当前代码中,将箭头函数传入this.client.on("connect", this.handleConnection);可正常运行,但直接传入this.handleConnection方法时会抛出以下错误:
错误信息
TypeError [ERR_INVALID_ARG_TYPE]: The "listener" argument must be of type function. Received undefined at checkListener (node:events:265:3) at _addListener (node:events:545:3) at WebSocketConnection.addListener (node:events:604:10) at WebSocketClient.handleConnection (/home/mohammad/projects/alphaTrader/digestion/wsConnector.ts:57:25) at WebSocketClient.emit (node:events:512:28) at WebSocketClient.emit (node:domain:489:12) at WebSocketClient.succeedHandshake (/home/mohammad/projects/alphaTrader/digestion/node_modules/websocket/lib/WebSocketClient.js:348:10) at WebSocketClient.validateHandshake (/home/mohammad/projects/alphaTrader/digestion/node_modules/websocket/lib/WebSocketClient.js:332:10) at ClientRequest.handleRequestUpgrade (/home/mohammad/projects/alphaTrader/digestion/node_modules/websocket/lib/WebSocketClient.js:261:14) at ClientRequest.emit (node:events:512:28) { code: 'ERR_INVALID_ARG_TYPE' }
完整代码
import { Logger } from "./logger"; enum WebsocketStatus{ CONNECTING = 'connecting', STOPPED = 'stopped', CONNECTED= 'connected', IDLE = 'idle' } interface WebsocketInterface{ getStatus(): WebsocketStatus connect(): void disconnect(): void sendMessage(message: string): void } export class WebsocketClient implements WebsocketInterface{ private status: WebsocketStatus; private url: string; private logger?: Logger; private client: webSocket.client; private connection?: webSocket.connection; constructor(url: string, logger?: Logger){ if(logger) this.logger = logger; this.url = url; this.status = WebsocketStatus.IDLE; this.client = new webSocket.client(); this.client.on("connectFailed", this.handleConnectionFailed); this.client.on("connect", this.handleConnection); } getStatus(): WebsocketStatus { return this.status; } connect(): void { this.client.connect(this.url); } disconnect(): void { this.connection?.close() } private handleConnectionFailed(error: Error){ this.logger?.logError(error.message); } private handleConnection(connection: webSocket.connection){ this.connection = connection; this.logger?.logInfo(`successfully connected to address ${this.url}`); this.connection.on("error", this.handleConnectionError); this.connection.on("close", this.handleConnectionClose); this.connection.on("message", this.handleConnectionMessage); } private handleConnectionError(error:Error){ this.logger?.logError(error.toString()); this.status = WebsocketStatus.STOPPED; this.connection?.close(); } private handleConnectionClose(){ this.logger?.logInfo("connection closed"); this.status = WebsocketStatus.STOPPED; this.connection?.close(); } private handleConnectionMessage(message: webSocket.Message){ this.logger?.logInfo(message.toString()); } sendMessage(message: string): void { this.connection?.send(message); } }
问题原因
直接传入类方法时,方法的this指向会在事件触发时改变,不再指向WebsocketClient的实例。比如client.on("connect", this.handleConnection),当connect事件触发时,handleConnection里的this会指向client对象,而非你的类实例。这导致handleConnection内部调用this.connection.on("error", this.handleConnectionError)时,this.handleConnectionError是undefined(因为client没有这个方法),所以抛出listener为undefined的错误。
箭头函数能正常运行是因为它没有自己的this,会继承外层作用域的this(即构造函数中的类实例),所以方法内部的this始终指向类实例。
解决方案
方法一:构造函数中手动绑定this
在构造函数里给每个用作回调的方法绑定类实例的this,确保方法被调用时this始终指向类实例:
constructor(url: string, logger?: Logger){ if(logger) this.logger = logger; this.url = url; this.status = WebsocketStatus.IDLE; this.client = new webSocket.client(); // 绑定this到类实例 this.handleConnectionFailed = this.handleConnectionFailed.bind(this); this.handleConnection = this.handleConnection.bind(this); this.handleConnectionError = this.handleConnectionError.bind(this); this.handleConnectionClose = this.handleConnectionClose.bind(this); this.handleConnectionMessage = this.handleConnectionMessage.bind(this); this.client.on("connectFailed", this.handleConnectionFailed); this.client.on("connect", this.handleConnection); }
方法二:用箭头函数定义类方法
将类中的方法改为箭头函数形式,箭头函数会自动绑定当前类实例的this,无需手动绑定:
private handleConnectionFailed = (error: Error) => { this.logger?.logError(error.message); } private handleConnection = (connection: webSocket.connection) => { this.connection = connection; this.logger?.logInfo(`successfully connected to address ${this.url}`); this.connection.on("error", this.handleConnectionError); this.connection.on("close", this.handleConnectionClose); this.connection.on("message", this.handleConnectionMessage); } private handleConnectionError = (error:Error) => { this.logger?.logError(error.toString()); this.status = WebsocketStatus.STOPPED; this.connection?.close(); } private handleConnectionClose = () => { this.logger?.logInfo("connection closed"); this.status = WebsocketStatus.STOPPED; this.connection?.close(); } private handleConnectionMessage = (message: webSocket.Message) => { this.logger?.logInfo(message.toString()); }
方法三:事件监听时用箭头函数包裹回调
在调用on方法时,用箭头函数包裹类方法,保留类实例的this:
constructor(url: string, logger?: Logger){ if(logger) this.logger = logger; this.url = url; this.status = WebsocketStatus.IDLE; this.client = new webSocket.client(); this.client.on("connectFailed", (err) => this.handleConnectionFailed(err)); this.client.on("connect", (conn) => this.handleConnection(conn)); }
同时,handleConnection内部的事件监听也要做同样处理:
private handleConnection(connection: webSocket.connection){ this.connection = connection; this.logger?.logInfo(`successfully connected to address ${this.url}`); this.connection.on("error", (err) => this.handleConnectionError(err)); this.connection.on("close", () => this.handleConnectionClose()); this.connection.on("message", (msg) => this.handleConnectionMessage(msg)); }
内容的提问来源于stack exchange,提问作者Mond666

