#Overwriting 'this' in Function.prototype.call() in classes.

106 messages ยท Page 1 of 1 (latest)

grave schooner
#

I'm trying to use Function.prototype.call() to pass an object to my existing function, but Typescript complains about this.

class Hub {

  private parseXMLError(xmlString: string) {
    xml2js.parseString(xmlString, (err, result) => {
      this = {
        code: result.code,
        message: result.error_message
      }
    })
  }

}

I call the method as such:

async getProcs() {
  let errObj = {};
  this.parseXMLerror.call(errObj, xmlString);
}

I can use this approach just fine in JavaScript.

pulsar herald
#

Oh hang on, why would you assign to this?

grave schooner
#

Same exact error.

But having this as a parameter kind of defeats the purpose of what I'm trying to do...

pulsar herald
#

Forget that for a moment

#

If this is a class instance, you can't assign an arbitrary object to it, that violates the typing

grave schooner
#

Does that mean that whilst inside a class I cannot use Function.prototype.call()?

pulsar herald
#

let me work out what this is

#

I feel this is a strange pattern

grave schooner
#

Thanks. Can pop in a voice chat and share screen if it helps in anyway.

pulsar herald
#

Can you make a TS Playground demo?

rapid geodeBOT
#
sandiford#0

Preview:```ts
class Hub {

private parseXMLError(this: xmlString: string) {
xml2js.parseString(xmlString, (err, result) => {
this = {
code: result.code,
message: result.error_message
}
})
}

}
async getProcs() {
let errObj = {};
...```

grave schooner
#

sure. I was literally just doing it

pulsar herald
#

Cool

#

So when you give a function a this argument, it's not a real argument, it just specifies the this context that the function is allowed to be called with. So it might be an answer.

grave schooner
#

Could I not have a function-scoped this that refers to that instance of the function?

pulsar herald
#

There is no instance of a function

#

There is only one, unless we are talking arrow functions in classes, that belong to the class instance

rapid geodeBOT
#
sandiford#0

Preview:ts function foo() { this = "foo" }

grave schooner
#

Ok, let's not include classes here for a second. Talking of functions in typescript directly.
I get the same error here.

rapid geodeBOT
#
adriancttnc#0

Preview:```ts
import * as xml2js from 'xml2js';

// class Hub {

// private parseXMLError(xmlString: string) {
// xml2js.parseString(xmlString, (err, result) => {
// /**
// * result = {
// * id = 06150,
// * message = 'NORIGHTS'
...```

pulsar herald
#

Are you going to use new Food ?

grave schooner
#

no

#

sorry, yes

pulsar herald
#

ah

grave schooner
#

The example does use it

pulsar herald
#

So last time I tried to use constructor functions in TS I gave up. I think it can be type, with this arguments

grave schooner
#

Should I instead just add another argument to my function and just pass errObj in by reference?

pulsar herald
#

You can sort of make progress like this

grave schooner
#

It'll achieve the same thing in this specific scenario

pulsar herald
#
type ProductObject = {
  name?: unknown
  price?: unknown
}
function Product(this: Partial<ProductObject>, name: any, price: any) {
  this.name = name;
  this.price = price;
}

type FoodObject = {
  category?: unknown
}
function Food(this: Partial<FoodObject>, name: any, price: any) {
  Product.call(this, name, price);
  this.category = 'food';
}
#

Really I think you just shouldn't be writing anything like this at all

#

Constructor functions are from the early days of JS, and are largely superced by classes today

#

and using call() or apply() is a very niche requirement

grave schooner
#

I'm trying to rewrite an app from vanilla JS to Typescript and just discovered Function.call() and wanted to integrate it in my reworked app.

pulsar herald
#

Rewrite it with classes if you can

#

Classes are a syntactic suger over constructor functions

#

Well might not be totally true, but 99%

grave schooner
#

That is what I was trying, but seems that the .call() conflicts with the class.

pulsar herald
#

why do we need to use call at all?

grave schooner
#

I don't. I just wanted to use it ๐Ÿคทโ€โ™‚๏ธ

#

I'm still expanding my knowdledge and skillset

pulsar herald
#

This looks to be a pattern that is largely incompatible with TS

#

You have a method parseXMLError in class Hub, where this will normally be a Hub or a subclass of Hub

#

Then we are using prototype.call to override the value of this to an arbitrary object. This is really making things complicated

torpid glacier
#

There's nothing wrong with .call in TS... but mutating this inside a .called function won't do anything useful anyway

grave schooner
#

Indeed. I can see how this complicates thigs.
Like I said, I had no reason to use .call() I just wanted to used it and have a working example of it in the new app.

torpid glacier
#

It's like doing:

function foo(y) {
    y = 1;
}

let x = 0;
foo(x); // x is still 0, reassigning the `y` variable inside the function doesn't affect it
pulsar herald
#
declare const xml2js: {
    parseString: (s: string, callback: (err: any, result: any) => void) => void
}

type XMLError = {
    code: number,
    message: string
}
function parseXMLError(this: Partial<XMLError>, xmlString: string) {
    xml2js.parseString(xmlString, (err, result) => {
        this.code = result.code,
        this.message = result.error_message
    })
}

async function getProcs() {
    const xmlString = '<root></root>'
    let errObj = {};
    parseXMLError.call(errObj, xmlString);
}
#

You can write something like that

#

Taking away Hub class which complicates things

torpid glacier
#

It's also just not valid JS to assign to this, I'm pretty sure

pulsar herald
#

But, it's not really useful, because errObj isn't typed after you run parseXMLError.call(errObj, xmlString);

#

So you are much better off returning a typed object

#

It is also async code without promises, so you don't know when it is complete...

#

Just, really bad code that you shouldn't be trying to copy

#

(not necessarily bad code, in old JavaScript, without the tools we have today, but based on current practices, not a good way)

grave schooner
#

Very well. Then I shall not use this and I'll define a proper typed argument.

rapid geodeBOT
#
sandiford#0

Preview:```ts
declare const xml2js: {
parseString: (
s: string,
callback: (err: any, result: any) => void
) => void
}

type XMLError = {
code: number
message: string
}
function parseXMLError(
this: Partial<XMLError>,
xmlString: string
) {
xml2js.parseString(xmlString, (err, result) =
...```

pulsar herald
#

Here is an implementation using a promise and returing a typed object

#

Well, they are both there side by side

#

And because it's using a promise, you can await it. The other code would just update the errObj at an arbitrarily time in the future without letting you know when it happes

#
class Product {
  name: unknown
  price: unknown
  constructor(name: unknown, price: unknown) {
    this.name = name
    this.price = price
  }
}

class Food extends Product {
  category: unknown
  constructor(name: unknown, price: unknown, category: unknown) {
    super(name, price)
    this.category = category
  }
}

This is what I figured you were trying to do with Food and Product, using classes

grave schooner
#

I guess that solves my issue, or at the very least it provides me with an answer I can happily take home.

#

Normally I'd do things similar to the way you've done, but I wanted to try something new.

pulsar herald
grave schooner
#

It makes sense. It complicates things more than it should.

pulsar herald
#

Sometimes you really need to override the this value, and you can do that

#

I'll see if I can find a genuine usage in my libs somewhere

grave schooner
#

Much appreciated

pulsar herald
#
function elementDelete(this: TungstenBaseComponent<any, any>) {
    if (typeof this.componentWillUnmount === 'function') this.componentWillUnmount()
    if (typeof this._deleting === 'function') this._deleting()
    this.__disposables?.forEach( (d: Disposable) => {
        d[Symbol.dispose]()
    })
}

export abstract class TungstenBaseSimpleStoreComponent<
    Props extends TungstenComponentPropsBase,
    RouteData,
    StoreSchema extends TungstenComponentStoreConstraint
> extends TungstenSimpleComponent<Props, RouteData> {
    abstract store: ComponentStore<StoreSchema>
    constructor(pkg: ConstructPackage<Props, RouteData>) {
        super(pkg)
    }
    __proxyStore = __proxyStore
    __delete = () => {
        elementDelete.call(this) // !!!
        unsubscribeAll(this)
    }
}


export abstract class TungstenBaseStatefulStoreComponent<
    Props extends TungstenComponentPropsBase,
    State extends TungstenComponentStateConstraint,
    RouteData,
    StoreSchema extends TungstenComponentStoreConstraint
> extends TungstenStatefulComponent<Props, State, RouteData> {
    abstract store: ComponentStore<StoreSchema>
    constructor(pkg: ConstructPackage<Props, RouteData>) {
        super(pkg)
    }
    __proxyStore = __proxyStore
    __delete = () => {
        elementDelete.call(this) // !!!
        unsubscribeAll(this)
    }
}
#

Here I'm using .call to share a function between 2 different classes

#

They have different parent classes, so I can't use inheritance

#

And TS mixins are kinda complicated, so I guess I did this

#
    positionOf: (node: HTMLElement | Text) => number = (node: HTMLElement | Text) => {
        const pos = Array.prototype.indexOf.call(this.parent.childNodes, node)
        return pos
    }

this.parent.childNodes has a type of NodeListOf<ChildNode>, with is a browser/DOM thing

#

NodeListOf<ChildNode> doesn't have indexOf, but the Array indexOf can work on it, because it is array like

#

So I'm calling the Array indexOf method and passing the list as it's this binding to run the array method on the list

#
Object.prototype.hasOwnProperty.call(userConfig, key)

userConfig is an object.
this is sometimes used because user.hasOwnProperty could be overridden, so accessing hasOwnProperty from Object.prototype guarantees that we get the original function.

#

So yeah, really quite specific, specialist uses

grave schooner
#

This is big-brain stuff. It makes sense to use it in this case, however I think with my current skillset I'm WAY far from doing something similar to you ๐Ÿ˜‚

#

As in, this stuff makes sense when someone explains it to you, but I wouldn't be able to come up with it by myself.

pulsar herald
#

Yeah it's very rare to use it. It's good to know and keep in the back of your mind that you can call a function, and provide the this context to it. Once in a blue moon you'll have a difficult challenge and might think "oh! how about .call / .apply!"

#

But apart from that, not really.

grave schooner
#

Fair. Thanks a lot for the help provided!

pulsar herald
#

I do like the first use case though. You can do mixins as a way to simulate multiple inheritance instead. But if you check it TS mixins, they are pretty ugly.

pulsar herald
grave schooner
#

Never even heard of TS mixins

#

I'll have to look them up

pulsar herald
#
function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
          Object.create(null)
      );
    });
  });
}
#
class Jumpable {
  z = 1;
  jump() {
    console.log('Jumped');
  }
}

// Including the base
class Sprite {
  x = 0;
  y = 0;
}

interface Sprite extends Jumpable {}
applyMixins(Sprite, [Jumpable]);
#

basically lets you mix 2 classes together

#

But honestly I've never sat down with it for long enough to get my head around it.

#

Its for situations when you want to inherit form multiple classes, which you can't do ordinarily.

grave schooner
#

Oh! That does make sense!

#

To be honest I can't wait for a time where I'd need to do that (means I've probably advanced considerably in my TS career)

pulsar herald
#

I've done a lot of nerding out over TS. E.g. those classes are from my react-like framework.

#

Building apps tends to involve less complex TS

#

most of the time

grave schooner
#

I can tell ๐Ÿ˜‚

pulsar herald
#

Not sure I actually needed to use call to share the function in those classes though to be honest. Probably would have been fine just assigning the function. Oh well.

grave schooner
#

One of the things I like more about learning new stuff is going back and refactoring things I've done in the past to be either more efficient or more readable or to give them any other benefit

#

Btw, I'll use your example with promises. xml2js also has a parseStringPromise which basically does what you've done in parseXMLError2

#

!resolve