sessionStorage proxy class scope
Asked Answered
M

1

1

I am wondering how to access the native sessionStorage scope from within my custom methods.

My example:

https://jsfiddle.net/3mc7ao7j/1/

At line 3 I would like to be able to proxy through to my native sessionStorage after perform my mutation on the data.

How do I access that scope again though? I know I can just call:

sessionStorage.setItem()

But that simply works because it is globally available and it feels wrong to do that. Mainly because I would also want to know how to do this without an object that is not globally available so I can learn how to proxy other objects.

Mcpherson answered 21/9, 2017 at 8:52 Comment(0)
H
4

Some window objects like location are read-only, it can be useful to create abstractions over them or make use of DI for testability.

sessionStorage is supposed to be used as is. Usually no abstractions over it are needed. If its functionality should be extended or modified, a custom class can be created. It can implement Storage interface or have its own.

The use of Proxy is unjustified here. It is slow and restricts the code from being used in ES5 environments.

Custom class or object can just wrap original sessionStorage methods. Since Storage API is small, wrapper class results in ~20 lines of code:

class CustomSessionStorage {
  get length() {
    return sessionStorage.length;
  }

  getItem(key) {
    return sessionStorage.getItem(key);
  }
  ...
}

sessionStorage object is exotic. Although it inherits from Storage, Storage is not a constructor, and sessionStorage methods should be bound to sessionStorage directly, so it's not possible to make it work just with CustomSessionStorage.prototype = sessionStorage. Additionally, sessionStorage has length property descriptor that should be bound as well.

A more general way to extend it is to provide a base class that wraps original methods and can be extended further:

function BaseSessionStorage() {}

for (let prop of Object.getOwnPropertyNames(Storage.prototype)) {
  if (typeof sessionStorage[prop] === 'function') {
    // bind all sessionStorage methods
    BaseSessionStorage.prototype[prop] = sessionStorage[prop].bind(sessionStorage);
  } else {
     // a proxy for other props, length is read-only getter
     Object.defineProperty(BaseSessionStorage.prototype, prop, {
       get: () => sessionStorage[prop],
       set: val => { sessionStorage[prop] = val }
     });
  }
}

class CustomSessionStorage extends BaseSessionStorage {
  getItem(key) {
    return super.getItem(key);
  }
  // the rest of API is inherited
}
Hannus answered 21/9, 2017 at 9:40 Comment(10)
I need a proxy if I want to implement custom methods for a native object but do not want to implement all of them. There is no other way. If I want to JSON.stringify data whenever I set something and JSON.parse it get I get an item 90% of the time I am not going to do this everytime. I simply want my proxylayer to take care of this.Mcpherson
I don't see a real need for Proxy here. This can be handled by a regular class (or probably object, since a storage is supposed to be a singleton). Regarding the original question, How do I access that scope again though?, you need to refer it as sessionStorage.setItem() explicitly inside setItem. You can use this in proxy methods, but only if a method is called differently: set(k, v) { this.setItem(a, msg) }.Hannus
If you simply wrap the original you have to also implement all methods if you want it to behave like the native sessionStorage object.Mcpherson
Yes. It's not that hard, that's the way it's usually done in custom storages, As you can see, there are only 5 methods and length prop. Storage is quite special and limits the ways it can be extended. And Proxy doesn't make it any easier if you want to extend original methods.Hannus
The implementation of all methods is the exact thing I want to prevent though . That is why I stated so I can learn how to proxy other objects at the end of the post. This is not just about this particular use case but also others. If I were to implement this for Google Maps I wouldn't want to have to implement all 50 methods just because I need to modify 4.Mcpherson
The question is about sessionStorage. It is exotic and requires special treatment. You shouldn't avoid it because that's how it should be done here. Even if you create a bogus class to extend, class BaseSS {}; BaseSS.prototype = /* all Storage methods that are bound to sessionStorage */, you still need to take care of length. For non-exotic objects the inheritance is usually plain and simple. Again, no Proxy is needed, everything is naturally handled by JS inheritance.Hannus
I've updated the answer with the recipe I meant. It will work for both exotic and regular objects (although less hacky inheritance methods are more preferable for regular objects).Hannus
Thanks I will try it out.Mcpherson
For the record, the issue regarding JSON.stringify() can be resolved by implementing a toJSON() member method on the custom class.Harmonyharmotome
@PatrickRoberts That's true. I guess the OP tries to make a convenience wrapper for the storage, so it could automatically serialize and deserialize objects.Hannus

© 2022 - 2024 — McMap. All rights reserved.