here is the simplest form of my problem:
class Service1 { constructor() { this.name = 'service1' } getThisName() { console.log('Name: ' + (this && this.name)) } } const service1 = new Service1(); service1.getThisName() // 'service1' => :) function mapper(fn, ...params) { this.name = 'mapper'; // ...params can be parsed or generated here fn(...params); } mapper(service1.getThisName) // undefined => :'(
I know I can fn.bind(service1)
in the mapper
function to solve the problem, but as fn
is dynamic, I would prefer not to do that.
I have tried searching on how to get the parent class from child method but get no results.
I want mapper
to be able to call a method of a class (or object) without loosing the this reference in a readable and straightforward way if possible. mapper
is always called in the same context.
Is there a way in javascript to solve this problem ?
What I have tried
function mapper(fn, serviceClass) { fn.bind(serviceClass)(); } mapper(service1.getThisName, service1) // OK but is not readable and seems hacky
function mapper(serviceClass, fnName) { serviceClass[fnName](); } mapper(service1, 'getThisName') // OK but autocompletion in the IDE don't work
function mapper(fn) { fn(); } mapper(service1.getThisName.bind(service1)) // the "best practice" but in my case not enougth readable
Real use case context
In the real use case scenario, the mapper
is called api2service
. As the name suggests, it is used with expressJs to map api routes to services. Here is a simplified version of the code:
app.get( 'get/all/users', // api endpoint api2service( userService.getAll, // getAll take filter as the first param ['req.query'] // req.query is the filter and is mapped AND parsed as the first param of the service function. Eg: {name: 'blah'} ) )
That code is repeated a lot of time and always called in the same context, that’s why I need something readable over the strict respect of good practices.
Advertisement
Answer
Until the bind operator proposal is implemented, there’s not much you can do about this. Apart from your attempts, you can automatically bind methods at construction time (see also https://github.com/sindresorhus/auto-bind):
function autoBind(obj) { let proto = Object.getPrototypeOf(obj); for (let k of Object.getOwnPropertyNames(proto)) { if (typeof proto[k] === 'function' && k !== 'constructor') obj[k] = proto[k].bind(obj) } } class Service1 { constructor() { this.name = 'service1' autoBind(this); } getThisName() { console.log('Name: ' + (this && this.name)) } } function mapper(fn) { fn(); } let srv = new Service1 mapper(srv.getThisName)
or use a binding Proxy:
function Bound(obj) { return new Proxy(obj, { get(target, prop) { let el = target[prop]; if(typeof el === 'function') return el.bind(target) } }) } class Service1 { constructor() { this.name = 'service1' } getThisName() { console.log('Name: ' + (this && this.name)) } } function mapper(fn) { fn(); } let srv = new Service1 mapper(Bound(srv).getThisName)