In the process of describing the next set of tests for the Node.js module, I caught myself thinking "again type checking". Each parameter of a class method, each property set using a setter must be checked. Of course, you can just score or supplement everything with code that implements checks or try to describe everything with decorators. But this time we will do a little differently.
The arrival of the ES6 specification and its implementation in various engines gives us a lot of amazing things, I think it's stupid to stand aside from this holiday. Armed with the desire to dive deeper into the new specification and simplify your life a bit, we will try to implement type analysis using such a wonderful thing as Proxy . Of course, we will use other ES6 buns natively supported by Node.js version 6.1.0, such as: classes, maps, arrow functions, etc.
Install the npm i typedproxy
. We connect the module
// 1. const Typed = require('typedproxy');
We create a class, we use static methods, methods, static properties and properties. When describing the parameters of methods and setters, we use a special syntax.
Namely: each parameter name used in methods (including static) must begin with a sequence of characters corresponding to the type. A type is nothing more than a property of a so-called type object . Where the name of the property corresponds to the name of the type, and the value of the property is a function that implements the verification of the transferred value. In other words, you can define as many of your own types of variables.
Read more about object types . This object should list all types that are used or planned for use in your class. If you forget to fulfill the specified condition, it will result in a RangeError during execution.
And so little code to understand the principles:
// 2. , . class TestClass { // 3. myRange. constructor(myRangeValue){ this.value = myRangeValue; } }; // 4. . myRange. const types = { 'myRange' : (value) => { if(value < 0 || value > 10) { throw new TypeError(`parameter must be more than 0 and less than 10, not ${value}`); } } };
As we can see, myRange Value is the name of the parameter, the check of which is defined in the property of the type object with the corresponding name myRange .
Now, in order to enable type checking, it is necessary to make the class typed (this concept is of course used within the framework of the module used, it is not necessary here to attract concepts from the specification). And we do it like this, having the previously described TestClass class and types:
// 5. const TypedTestClass = new Typed(TestClass, types);
Above, we got a new class, TypedTestClass, which is actually a proxy instance, but more on that later . We use it instead of TestClass, that is, we create instances, we call static methods of both the class itself and its instance. In general, doing all that we wanted to do with the original TestClass class.
// 6. . /*ok - */ const instance1 = new TypedTestClass(5); /*TypeError - */ const instance2 = new TypedTestClass(11); /*RangeError - */ const instance3 = new TypedTestClass(); /*RangeError - */ const instance3 = new TypedTestClass(1, 2);
As you can see, passing an invalid parameter type now causes an error. Passing an incorrect number of parameters (no matter whether they meet the requirements of the type or not) also causes an error.
extends
) you need to type the final class, not the whole chain. Well, firstly, why unnecessary variables and extra work, and secondly, we just will not work. // 1. const Typed = require('typedproxy'); // 2. , . class TestClass { // 3. myRange. constructor(myRangeValue){ this.value = myRangeValue; } }; // 4. . myRange. const types = { 'myRange' : (value) => { if(value < 0 || value > 10) { throw new TypeError(`parameter must be more than 0 and less than 10, not ${value}`); } } }; // 5. const TypedTestClass = new Typed(TestClass, types); // 6. . /*ok - */ const instance1 = new TypedTestClass(5); /*TypeError - */ const instance2 = new TypedTestClass(11); /*RangeError - */ const instance3 = new TypedTestClass(); /*RangeError - */ const instance3 = new TypedTestClass(1, 2);
For those who understand the code thousands of words and a couple of pictures: a project with tests here . For those who have retained the desire to understand how it works in words, I will try to explain further.
As can be seen in the figure and as mentioned above, it is necessary to establish a direct relationship between the names of the parameters of the methods and the types used in our class. That is, the name of the parameter must begin with a sequence of characters corresponding to the type.
It is necessary that the function producing the type check would work (let's talk about it a little later) . In principle, if the implementation of this function does not suit you, you can pass your third parameter when creating a typed class.
const Typed = require('typedproxy'); class TestClass { // ... }; const types = { // ... }; const TypedTestClass = Typed(TestClass, types, (types, someFunction, ...args) => {/* */});
When typing a class, a new Proxy is created and returned. This is the proxy and is a class performing type analysis. Its essence consists in applying the function of type checking and determining the necessary traps for intercepting a call to static methods, creating new instances, etc.
The type checking function (green-red squares in the figure) works as follows:
New proxy contains only three pitfalls: get , set and construct .
new
operator, checks the types of parameters passed to the constructor. After that, it creates an instance of the original class and a proxy based on it (with two get and set traps that work similarly with the above methods 1 and 2).Of course, this looks quite monstrous, and in fact it is. This module displays only the idea that requires improvements and revisions. I don’t even want to think about performance; rather, you can use it by sacrificing it for the sake of convenience and time savings.
Source: https://habr.com/ru/post/301122/
All Articles