Egee
Egee

Reputation: 77

JavaScript/NodeJS - Adding a promise as an extension method

There could be a simple answer for this but I've only ever had to use extension methods (are they even called that in JS?) in C#..

I have a 3rd party library that uses events. I need a function to be called after the event is called. I could do this easily via a promise but the 3rd party library was written before ES6 and does not have promises built in. Here's some sample code -

wRcon.on('disconnect', function() {
  console.log('You have been Disconnected!')
})

Ideally I would like to be able to implement something like this -

wRcon.on('disconnect', function() {
  console.log('You have been Disconnected!')
}).then(wRcon.reconnect())

So to summarize my question, how do I extend wRcon.on to allow for a promise (or some other callback method)?

Upvotes: 1

Views: 197

Answers (3)

T.J. Crowder
T.J. Crowder

Reputation: 1074495

Three/four options for you, with the third/fourth being my personal preference:

Replacing on

You could replace the on on the wRcon object:

const original_on = wRcon.on;
wRcon.on = function(...args) {
    original_on.apply(this, args);
    return Promise.resolve();
};

...which you could use almost as you showed (note the _ =>):

wRcon.on('disconnect', function() {
  console.log('You have been Disconnected!');
}).then(_ => wRcon.reconnect());

...but I'd be quite leery of doing that, not least because other parts of wRcon may well call on and expect it to have a different return value (such as this, as on is frequently a chainable op).

Adding a new method (onAndAfter)

Or you could add a method to it:

const original_on = wRcon.on;
wRcon.onAndAfter = function(...args) {
    wRcon.on.apply(this, args);
    return Promise.resolve();
};

...then

wRcon.onAndAfter('disconnect', function() {
  console.log('You have been Disconnected!');
}).then(_ => wRcon.reconnect());

...but I don't like to modify other API's objects like that.

Utility method (standalone, or on Function.prototype)

Instead, I think I'd give myself a utility function (which is not "thenable"):

const after = (f, callback) => {
    return function(...args) {
        const result = f.apply(this, args);
        Promise.resolve().then(callback).catch(_ => undefined);
        return result;
    };
};

...then use it like this:

wRcon.on('disconnect', after(function() {
  console.log('You have been Disconnected!');
}, _ => wRcon.reconnect()));

That creates a new function to pass to on which, when called, (ab)uses a Promise to schedule the callback (as a microtask for the end of the current macrotask; use setTimeout if you just want it to be a normal [macro]task instead).

You could make after something you add to Function.prototype, a bit like bind:

Object.defineProperty(Function.prototype, "after", {
    value: function(callback) {
        const f = this;
        return function(...args) {
            const result = f.apply(this, args);
            Promise.resolve().then(callback).catch(_ => undefined);
            return result;
        };
    }
});

...and then:

wRcon.on('disconnect', function() {
  console.log('You have been Disconnected!');
}.after(_ => wRcon.reconnect()));

And yes, I'm aware of the irony of saying (on the one hand) "I don't like to modify other API's objects like that" and then showing an option modifying the root API of Function. ;-)

Upvotes: 0

Kryten
Kryten

Reputation: 15760

Promises and events, while they seem similar on the surface, actually solve different problems in Javascript.

Promises provide a way to manage asynchronous actions where an outcome is expected, but you just don't know how long it's going to take.

Events provide a way to manage asynchronous actions where something might happen, but you don't know when (or even if) it will happen.

In Javascript there is no easy way to "interface" between the two.

But for the example you've given, there is an easy solution:

wRcon.on('disconnect', function() {
  console.log('You have been Disconnected!')
  wRcon.reconnect()
})

This is perhaps not as elegant as your solution, and breaks down if your intention is to append a longer chain of .then() handlers on the end, but it will get the job done.

Upvotes: 2

Alex Robertson
Alex Robertson

Reputation: 1471

You could wrap the connection in a way that would create a promise:

const promise = new Promise((resolve) => {
  wRcon.on('disconnect', function() {
    resolve();
  })
}).then(wRcon.reconnect())

However, this does not seem like an ideal place to use Promises. Promises define a single flow of data, not a recurring event-driven way to deal with application state. This setup would only reconnect once after a disconnect.

Upvotes: 0

Related Questions