Can not access private field through public get via proxy - javascript

When getting Data from a VueX Module via Getter the Object is wrapped with a Proxy that throws following error:
TypeError: attempted to get private field on non-instance
when trying to read a private property with a public getter.
class Example {
#privateField;
get privateField() {
return this.#privateField;
}
}
computed(){
getInfoForSpecificItem() {
const arrayOfInstantiatedExamples = this.$store.getters['getArrayOfInstantiatedExamples'];
for (let i = 0; i < arrayOfInstantiatedExamples.length; i += 1) {
// trying to access it in this condition throws the error since the Object
// has been wrapped with a Proxy coming from Vue (I guess)
if (arrayOfInstantiatedExamples[i].privateField === 'something') {
// does something
}
}
}
}
Why am I not able to read the property, and how can I create an instance from the proxy target.
logging the Proxy Object reveals, that the target has all information, yet trying to read it via getter doesn't work.
Are there any other options than making the fields public?

You are defining a class not a function. So remove the parentheses().
class Example {
You cannot define variable with #. Only $ and _ are allowed to use in a variable. So replace # with $ like this $privateField;
Classes are blueprints, not a valid object. First make an object from this class.
const proxyWrappedExample = new Example;
After you log this then you can avoid the error but it logs undefined.
Your code should look like
class Example {
$privateField = 'something';
get privateField() {
return this.$privateField;
}
}
const proxyWrappedExample = new Example;
// I receive the Object (which has been instantiated earlier) wrapped in a proxy Object.
// Trying to access the property throws the error.
// e.g.:
console.log(proxyWrappedExample.privateField); // 'something'
$privateField without any value will look like following.
class Example {
$privateField;
get privateField() {
return this.$privateField;
}
}
const proxyWrappedExample = new Example;
// I receive the Object (which has been instantiated earlier) wrapped in a proxy Object.
// Trying to access the property throws the error.
// e.g.:
console.log(proxyWrappedExample.privateField); // 'undefined'

Related

Object.create() and native private fields

When I create instance of a class via Object.create(Example.prototype) to bypass its constructor, it is no longer possible to work with native private fields:
class Example {
#myPrivateProp;
setup() {
this.#myPrivateProp = 1;
}
}
const e1 = new Example();
const e2 = Object.create(Example.prototype);
console.log(e1.setup()); // works
console.log(e2.setup()); // fails with Uncaught TypeError: Cannot write private member #myPrivateProp to an object whose class did not declare it
Is there a way to make this work, while maintining the invariant of not calling the constructor?
For context you can see this issue: https://github.com/mikro-orm/mikro-orm/issues/1226
What you are defining here is not a private field but a private instance field. A private instance field is there not to be exposed and manipulated by others except that very instance. You shouldn't be able to clone that very instance along with it's state, manipulate it and replace the original one.
You have two options;
A static private field that belongs to the Example class which can be accessed and manipulated mutually by any instance of that class or even by objects created like Object.create(Example.prototype).
such as;
class Example {
static #SECRET = 0;
constructor(){
}
addOne() {
return ++Example.#SECRET;
}
}
i1 = new Example();
i2 = new Example();
console.log(i1.addOne()); // <- 1
console.log(i2.addOne()); // <- 2
i3 = Object.create(Example.prototype);
console.log(i3.addOne()); // <- 3
Example.#SECRET = 4; // <- Uncaught SyntaxError: Private field '#SECRET' must be
// declared in an enclosing class
The private instance field belongs to the instance and can only be accessed and manipulated by that very instance. How this is achieved is exactly seen in your example code. Every single instance will have it's own #myPrivateProp which, if you ask me, is a beautiful thing. For example one can instantiate many individual Queues and their operationaly critical properties wouldn't be exposed.

How do I invoke a function whenever a property of an object is read?

We can use this pattern to create hooks when reading a property from an object:
const obj = {};
Object.defineProperty(obj, 'x', {
get: function () {
return require('x');
}
});
I am wondering if there is a way to create a hook when reading any and all properties on an object such as obj?
For example, when code tries to access property 'y' which doesn't exist on obj yet, the hook will still get invoked.
That's the key part: if I knew all of the object's properties ahead-of-time, I could loop through them. But in this case, properties may be added to the object later, or an unanticipated property may get read (end-user will call something unexpected.)
I think you're looking for a Proxy which is a new ECMAScript 2015 feature and allows you to define custom behavior for certain internal methods such as getting properties. Implementing the handler.get trap allows for a certain function to be called whenever a property is accessed:
const obj = {};
const proxyObj = new Proxy(obj, {
get(target, property, receiver) {
console.log('accessed');
}
});
proxyObj.foobar; //A property is accessed and the console is logged to
Here, accessing any property of proxyObj will result in a log in the console. All "trap" means here is that the getting of the property value is intercepted, or trapped, and then you can invoke custom behavior when it happens. target here is the target object (obj), property is the property being accessed and receiver is the proxy. To apply it here, check if property is y:
if(property === 'y') {
...
}
It doesn't matter if the property exists or not, the trap is still invoked. There's a variety of other traps you can set such as setting a property, using the in operator, using the delete operator, etc. You can see them all at MDN.
#Andrew Li beat me to it, but here is a real example, which is my use case:
// async and bluebird libraries are both installed in my local project
let obj = {};
const proxy = new Proxy(obj, {
get(target, property, receiver) {
return require(property);
}
});
const {async, bluebird } = proxy;
console.log('bluebird => ', bluebird);
now I can inject this proxy object anywhere, and myself or the end user can easily access dependencies. The purpose is so that you don't need to pre-load dependencies if you don't actually need them. It also saves a few lines of code here and there.

How to save Javascript object to Firebase if object has functions/methods attached?

I'm trying to synchronize the data attributes of a javascript object to Firebase. However, when I attach methods to my object Firebase throws an error:
Error: Firebase.set failed: First argument contains a function in property
I'm looking for a nice clean way to sync my objects without having to strip out all the methods, or create a data attribute within the object.
My Javascript constructor
var MyClass = function() {
this.foo = "bar"; // an example attribute. No problem for Firebase
}
// add a method. Firebase will throw an error because of this method.
MyClass.prototype.myMethod = function () {};
Create a new object
var myInstance = new MyClass();
Firebase throws an error
firebase.database().ref().set(myInstance);
Any ideas for a clean way to make something like this work?
This should do the trick:
var json = JSON.parse(JSON.serialize(myInstance));
firebase.database().ref().set(json);
But in general, I'd recommend keeping your code separate from your data. Objects from your Firebase Database should not contain functions.

Sequelize getter not called when I use find

I have getters defined within the property definition of my model, they look like this:
timeReported: {
type: DataTypes.DATE,
defaultValue: Sequelize.NOW,
get() {
const input = this.getDataValue('timeReported')
const output = moment(input).valueOf()
return output
},
set(input) {
var output = moment(input).toDate()
this.setDataValue('timeReported', output)
},
validate: {
isBefore: moment().format('YYYY-MM-DD')
}
}
I have tried adding the getter to getterMethods in the options instead of within the property:
getterMethods: {
timeReported: function () {
debugger
const input = this.getDataValue('timeReported')
const output = moment(input).valueOf()
return output
}
}
I find that the setters are executed correctly when I save because I use timestamps as the request payload. I also find that validations work correctly.
Interestingly the afterFind hook is called correctly and it does show that customGetters have been registered as existing on my model.
However, when I call the find method on my model like this:
const {dataValues} = yield models.Reporting.findOne({
where: {
reportNo: reportNo
}
})
My getter methods are not invoked to transform my data for presentation. How do I invoke these methods on the data fetched from the db before I can access it.
I have tried adding the raw:false property to the queryOptions along with where. That did not work.
NOTE: I fetch my data using co, I have to retrieve the dataValues property from the model. None of the examples seem to be using the property.
When using Sequelize,one should not use dataValues property directly as I did. This will give me access to the underlying data stored in the table without invoking the getters.
To invoke all the getter functions on the entire instance:
const data = instance.get()
This will also populate any virtual fields that one has defined in the model.
To invoke the getter for an individual property:
const propValue = instance.get('timeReported')

Promises, Angular2 and scope

I have an Angular2 component that needs to get an ID from an external service. This service returns a promise so I need to await the Promise to get the data back before proceeding. The component looks something like this:
export class AddTodoComponent implements OnInit {
public todo:TodoItem;
constructor(private todoService: TodoService,
private router: Router) {
this.todo = new TodoItem();
}
saveItem(){
this.todoService.getItems().then(t=>{
this.todo.item.id = t.length;
this.todoService.addItem(this.todo);
});
}
}
However, when the saveItem function executes, I get an error in the console as follows:
EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot set
property 'id' of undefined
The service just returns a Promise.resolve(data) with the items I need, and I have verified that is returning the data as expected. However, it now seems that inside the ‘thennable’ of the promise I can no longer access this.todo, assumedly because this now refers to the promise rather than the class.
What is the correct/preferred means of solving this problem?
The problem is not to do with the value of this - it still refers to the AddTodoComponent class instance thanks to the use of "fat arrow" functions (=>).
Instead, the problem seems to be that there is no item property of this.todo, instead, so you can't then set this.todo.item.id.
Because the todo is the item
this.todo = new TodoItem();
the this.todo.item (the property item of the TodoItem instance)... is undefined. So, it should be init first (anyhow, inside of constructor, or outside as below)
this.todo.item= this.todo.item || {} ; // just init item
this.todo.item.id = t.length;
The error does not indicate this to not point to the current instance of AddTodoComponent but instead that item of this.todo is undefined. The error seems to be in new TodoItem() not to define item.

Categories