Reputation: 25
I have the following code where I want to invoke the instance method connect
before proceeding with the invocation of every other instance method of the TelegramClient
class. How can this be achieved by making use of Proxy
. As of now the connect
method does not get invoked.
class TelegramClient {
async connect() {
console.log("Connecting to Telegram...");
// Simulate some asynchronous work
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Connected!");
}
sendMessage(message: string) {
console.log(`Sending message: ${message}`);
}
}
const telegramClient = new TelegramClient();
// Create a Proxy for the Telegram client
const proxiedTelegramClient = new Proxy(telegramClient, {
async apply(target, thisArg, argumentsList) {
await target.connect(); // Connect to Telegram before invoking the method
const result = Reflect.apply(target, thisArg, argumentsList);
return result;
},
});
proxiedTelegramClient.sendMessage("Hello, Telegram!");
The expected output was ...
Connecting to Telegram...
Connected!
Sending message: Hello, Telegram!
Upvotes: 1
Views: 135
Reputation: 13366
A viable approach which does not utilize a proxy was to use a generic implementation of an async around
method-modifier. The latter can be seen as a specialized case of function-wrapping.
Such a modifier accepts two functions, proceed
and handler
as well as a target
-object as its 3 parameters. It does return an async function which again is going to return the awaited result of the (assumed async) handler
(callback) function. The latter does get invoked within the context of the (optionally) provided target
while also getting passed the proceed
-function, its own handler
-reference and the modified function's arguments
-array.
Thus, based on such a modifier, the OP could achieve the expected behavior by modifying e.g. a client instance's sendMessage
-method like this ...
// client instantiation.
const telegramClient = new TelegramClient;
// client's handler modification.
telegramClient.sendMessage = asyncAround(
telegramClient.sendMessage,
ensureConnectedClient,
telegramClient,
);
... where ensureConnectedClient
is the handler
function which implements exactly what the OP is looking for ...
"... [ensure a] connect[ed client] before proceeding with the invocation of every other instance method of the TelegramClient class"
... Example code ...
// implementation of the client specific async `around`-handler.
async function ensureConnectedClient(proceed, handler, args) {
const client = this;
// see:
// - [https://gram.js.org/beta/classes/TelegramClient.html#connected]
// - [https://gram.js.org/beta/classes/TelegramClient.html#disconnected]
// ... always ensure a connected client ...
if (client.disconnected()) {
console.log('+++ client needs to be reconnected +++');
await client.connect();
} else {
console.log('+++ client is still connected +++');
}
// ... before proceeding with any other method.
return (
await proceed.apply(client, args)
);
}
// client instantiation.
const telegramClient = new TelegramClient;
// client's method-modification.
telegramClient.sendMessage = asyncAround(
telegramClient.sendMessage,
ensureConnectedClient,
telegramClient,
);
// client's modified handler invocation.
telegramClient.sendMessage("Hello, Telegram!");
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
// client implementation.
class TelegramClient {
async connect() {
console.log("Connecting to Telegram...");
// simulate some asynchronous work.
await new Promise(resolve => setTimeout(resolve, 1500));
console.log("Connected!");
}
disconnected() {
// simulate a connect-state method via random boolean.
return Boolean(Math.floor(Math.random() + 0.5));
}
sendMessage(message) {
console.log(`Sending message: ${ message }`);
}
}
// implementation of an async `around` modifier.
function asyncAround(proceed, handler, target) {
return async function (...args) {
return (
await handler.call(target ?? null, proceed, handler, args)
);
};
}
</script>
Upvotes: 0
Reputation: 24181
The main problem here is that apply
is not async
, marking async
won't change that.
Rather than trying to intercept the sendMessage
method, another option is to auto-wrap the function inside the get
.
Below is an example, I've also use a Set
to make sure during get the method
does not get wrapped again. Also added a connected property, as sending another message shouldn't require another connect.
Update:
Keeping track of instances and proxy private data, is a little bit more involved than you might think. The context of this
with a proxy will change to the proxy, (makes sense), so below I've updated to hopefully account for multiple instances, and keeping data private no matter if your going via the proxy or accessing the target directly. The trick here is to use a Symbol to store the proxies original target, you can then use a weakMap
to keep your private instance data there.
const PROXY_TARGET = Symbol('PROXY_TARGET');
const _privData = new WeakMap();
const privData = t => {
let ret = _privData.get(t[PROXY_TARGET] ?? t);
if (!ret) {
ret = {
connected: false,
wrapped: new Set()
}
_privData.set(t, ret);
}
return ret;
}
class TelegramClient {
async connect() {
const priv = privData(this);
if (priv.connected) return;
priv.connected = true;
console.log("Connecting to Telegram...");
// Simulate some asynchronous work
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Connected!");
}
async sendMessage(message) {
console.log(`Sending message: ${message}`);
}
}
const handler = {
get(target, prop, receiver) {
const priv = privData(target);
if (prop === PROXY_TARGET) return target;
if (['connect'].includes(prop) || priv.wrapped.has(prop) ) {
return Reflect.get(...arguments);
}
const fn = target[prop];
function wrap() {
return target.connect().then(() => {
return fn(...arguments);
});
}
priv.wrapped.add(prop);
target[prop] = wrap;
return target[prop];
}
}
async function test(autoConnect) {
console.log(autoConnect ? 'Auto Connect' : 'Manual Connect');
const telegramClient = new TelegramClient();
const proxiedTelegramClient = new Proxy(telegramClient, handler);
if (!autoConnect) await proxiedTelegramClient.connect();
await proxiedTelegramClient.sendMessage("Hello, Telegram!");
await proxiedTelegramClient.sendMessage("Another message.");
}
//let try auto and manual connect.
test(true).then(() => test(false));
.as-console-wrapper { min-height: 100%!important; top: 0; }
Upvotes: 0
Reputation: 119
this can be resolved using a "get" instead of "apply" handler
//... previous code before the proxiedTelegramClient
// Create a Proxy for the Telegram client
const proxiedTelegramClient = new Proxy(telegramClient, {
get(target, property) {
if (property === 'sendMessage') {
return async function (...args) {
await target.connect(); // Connect to Telegram before sending the message
return Reflect.apply(target[property], target, args);
};
}
return target[property];
},
});
but if you want to keep code as it is, you can mak use of
class TelegramClient {
async connect() {
console.log("Connecting to Telegram...");
// Simulate some asynchronous work
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Connected!");
}
async sendMessage(message: string) {
await this.connect(); // Connect to Telegram before sending the message
console.log(`Sending message: ${message}`);
}
}
const telegramClient = new TelegramClient();
// Create a Proxy for the Telegram client
const proxiedTelegramClient = new Proxy(telegramClient, {
async apply(target, thisArg, argumentsList) {
return Reflect.apply(target, thisArg, argumentsList);
},
});
(async () => {
await proxiedTelegramClient.sendMessage("Hello, Telegram!");
})();
hope it helps
Upvotes: -1