Proxy, a new JavaScript ES6 feature
atyantik.com
atyantik.com
https://github.com/philbooth/hoopy
If the index overflows in either direction, loop back round to the start/end of the array. I wanted a circular array to use as a fixed-length buffer for receiving data in a streaming JSON parser I wrote:
https://github.com/philbooth/bfj/blob/61087c194d5675c75569a2...
declaraoids.findWhereNameEqualsX(persons, {x: "Mats"});
It will generate the function on the fly. User.find_by_username "alice"It's a shame it doesn't have declaraoids.makeMeASandwich() on the roadmap though.
In fairness, some RPC web frameworks do this in a completely non-ironic non-joke way. CakePHP "findBy" comes to mind.[1]
[1] https://book.cakephp.org/2.0/en/models/retrieving-your-data....
https://docs.spring.io/spring-data/commons/docs/current/refe...
Wasn't it an ActiveRecord thing to begin with? Spring got a lot of its more recent awful features by trying to copy Rails.
It always seemed ridiculous to put the lookup key and lookup value in two different places.
Nice to see Rails reconsider these things I once thought it would never surrender.
For example, they could've used maps:
find_by name: "foo"It's also just a limited DSL that has none of the capabilities of the host language, and can't be used to express e.g. complex queries.
But in Ruby it's based on method_missing not a proxy. I believe ruby got it's method_missing idea from Smalltalk. But there might be other precursors.
But the more I think about it, the more I can see that it's handy to have an official proxy interface for this, for the case of an external code needing to wrap your object.
Also, I've seen so many reinvented wheels for proxies, and usually bad ones, as it seems an easy problem to solve, but there are plenty of edge cases. Proxies avoid the recursive problem altogether.
Besides, __dundermethods__ tend to make the language hard to optimize.
Still, it's really overkill for simple cases like the ones addressed by Object.defineProperty (or @property for Python), as you create a huge layer of indirection for the entire wrapped object. So if you use such a Proxy, you better have a damn good reason.
Incidentally, PHP does it a similar way to Python with so-called "magic methods" but I can see the benefit to doing it this way.
Tough to know whether this is a fringe feature or actually useful on a regular basis without really digging in. Luckily, other comments here highlight some usecases.
Obviously you have things like sinon for stubbing out methods, but they do have to modify the original object, which I don’t think anybody loves.
But you can also wrap your object in a Proxy with handlers that stub certain methods.
You can also use proxies to implement validation and observability on objects. Here’s a very minimal example of a sort of Backbone-esque Model class with proxies: https://gist.github.com/kevincennis/25ba31b2e9f9c8b7a34047ba...
Note that this is either due to how the framework uses Proxy, rather than Proxy itself, or I'm doing it wrong and should do more reading up on it. It really could be the latter - given my level of experience and knowledge, I thought hard about posting this comment!
Ive seen the proxy thing trying to debugging. I didn't realize it was a set pattern/language feature.
You can use them conditionally to manipulate data before fetch or before storing. For example: you want to store the username when a person registers using the email id like userzyx@gmail.com then you can use it to generate username from it by truncating the later part and generate unique username which will be "userzyx" here.
Just open up your brains and see where further it can be used and possibilities are just unlimited.
class MyArray extends Array {
constructor(...args) {
super(...args);
return new Proxy(this, {
get: (target, name) => {
if (name && name.match && name.match(/^-\d+$/)) {
return Reflect.get(target, this.length + parseInt(name, 10));
}
return Reflect.get(target, name);
},
set: (target, name, value) => {
if (name && name.match && name.match(/^-\d+$/)) {
return Reflect.set(target, this.length + parseInt(name, 10), value);
}
return Reflect.set(target, name, value);
},
});
}
}
You could accomplish the same thing by creating explicit setters and getters, but proxies allow you to directly intercept and modify the behavior of property access, calling an object like a function, etc. This sort of flexibility allows you to create APIs that wouldn't be possible otherwise.The CustomPage class 'extends' through the use of a proxy a Page and Browser class, while adding its own methods on top. The proxy mediates access to the Custompage instance, and the Page and Browser. When calling a method on CustomPage, it first looks to see if the method exists on the CustomPage instance, then the Browser instance, then the Page.
I had originally wanted to subclass Page, which is implemented by the Puppeteer library, but the Browser class had a dozen direct references to the Page class. I would have had to sublcass the Browser as well and override all those methods to instead reference my CustomPage. The proxy seemed like an appropriate solution.
pretty cool library that is only possible because of es6 proxys
const user = await db.connect(sql => sql.getUserById(43))
The sql value is a proxy that looks for a file called 'getUserById.sql', reads sql out of it, sends the query to the database, and returns a promise for the result. With caching and some sugar methods, it's a pleasant way to use a database.It's pretty handy for generating data for unit tests, but wouldn't recommend it for production code since I've discovered Proxy is fairly slow.
It's not "new" at all, Firefox has had it since v18 (mid 2012 [0]), Chrome since v49 (early 2016 [1]), etc.
[0] https://en.wikipedia.org/wiki/Firefox_version_history
[1] https://en.wikipedia.org/wiki/Google_Chrome_version_history
FYI, if you're embedding a JSFiddle you could also just drop this in the HTML section then use console.log:
<script>console.log = function(str) { document.write(str, "<br/>") }</script>Hence document.write is used.
Though HTML could be used but that would just increase one more tab to show useless HTML. ;)
The easiest things to start using generators is to write sysadmin scripts that parse big files. Once you've done that, you get the memory benefit. Then the more you use it, the more you get the lazy evaluation benefit and you start using it elsewhere.
It's clearer in Python because:
- generators have there for ever
- we have unified way of iterating on things, and generators just work transparently
- you can start learning generators by just changing [] to () in your comprehension lists
So in JS it's much less obvious.
I just wrote a class to keep track of changes to an object using proxies yesterday. That way you can pass an object to a form, modify it in place and then roll back if needed. Also makes it easy to get deltas and only post the updates to the backend.
Very simple real-life use case: factor out logging and move it outside of normal logic of the object.
Python has had the __getattr__, __setattr__ and friends and once you start using them to be "creative" all static type checkers break down. They are usually frowned upon.
Let's hope typescript is able to interpret these Proxy objects and that the community don't abuse them for all kinds of strange things.
This is similar to the TC39 Optional Chaining Proposal https://github.com/TC39/proposal-optional-chaining
I am a bit intrigued about the way it's separated from the "nature" of the underlying Object though - in Python, we'd have a single canonical interface to the data, bound to the class in which the data was instantiated. Here, in ES6, it seems like the data might be attached to a base Object, and you could use that Object with any number of different proxies to provide polymorphic behavior.
Overall, these seem like very different philosophies of object-oriented programming. Which I guess makes sense given how the languages have evolved over time.
Seems to have a decent coverage for most users
And because this works on fundamental operators, a polypill is impossible.
However, if you are using Babel to transpile your JS or using Node.js this seems like it could be very useful.
https://www.npmjs.com/package/babel-plugin-proxy
> they do not have an equivalent in "old javascript"
Sure they do. Function calls.
So, between everyone having to transpile their client code or losing the few devs who need to support IE, I prefer the latter for my projects.
One use I've found is for library maintainers - a way to expose a large API without taking a hit for requiring the whole thing. We use it to consume parts of that API without having to require all the dependencies and subdependencies.
This makes me think of database triggers, which would imply that it's quite easy to implement dark patterns with Proxies.
For instance, take a look at this code example of opening a tab.
await browser.evaluateInBackground(createProperties => (
browser.tabs.create(createProperties)
), { url: 'https://intoli.com' });
The browser.evaluateInBackground() method is part of the Remote Browser API, and it simply evaluates code in a background script context in the browser. The browser.tabs.create() call, on the other hand, is part of the Web Extensions API. The vast majority of Remote Browser's power is provided through remote code execution and the Web Extensions API, so we really wanted to provide a more concise syntax for performing this sort of operation. Using proxies, we were able to make the browser object directly callable as a shorthand for background context evaluation. This code example is exactly equivalent to the previous one. await browser(createProperties => (
browser.tabs.create(createProperties)
), { url: 'https://intoli.com' });
That's a bit more concise, but the real sugar is in the next step of abstraction. Instead of explicitly evaluating a function in the background script context, you can simply treat the browser object in the client context as though it were the Web Extensions API browser object in the background script context. This code example is again exactly equivalent, and again accomplished using proxies. await browser.tabs.create({ url: 'https://intoli.com' });
The tabs.create method isn't hardcoded into the Remote Browser library. Instead, proxies are used to evaluate any non-local API calls in the background script context. This means that you automatically get full access to the version of the Web Extensions API that your browser supports, whether you're using Chrome, Edge, Firefox, or Opera.The one last piece of syntactic sugar is the shorthand for evaluating code inside of individual tabs. The syntax is very similar to how you evaluate functions in the background script context, you just need to first use square brackets to indicate the tab ID.
await browser[tab.id](createProperties => (
document.getElementById('some-link').innerHTML
), { url: 'https://intoli.com' });
This is also implemented using proxies. In fact, the same trap that handles the local Web Extensions API calls handles the tab access, and it differentiates between the two depending on whether or not the property name consists entirely of integers. In both cases, it returns another proxy that is able to handle either additional chaining or function evaluation.Anyway, sorry that this ended up a lot more long-winded than I was expecting. If this has piqued your interest in Remote Browser though, we made an interactive tour of the project that you should check out [3]. You can actually launch and control browsers from inside of your own browser in the tour, and it explains a lot more about the project philosophy and what you can do with it
[1] - https://developer.mozilla.org/en-US/Add-ons/WebExtensions/AP...
[2] - http://github.com/intoli/remote-browser/
[3] - https://intoli.com/tour
Successful features are those that surprise in the easy direction (arrrow functions) rather than in the hard direction (proxies).