How do I use and apply JavaScript decorators? - node.js

I am trying to understand how to use decorators in a very simple piece of code, so I can apply this concept to my bigger project. Taking cue from Addy Osmani's article here, I created a simple piece of code as below.
Say, I have a class called Cat, with a meow() method, I want to decorate it with some logging, as below.
class Cat {
#logger
meow() { console.log( ' Meeeoow! ') }
};
function logger(target, key, descriptor) {
console.log("Cat snarling...");
return descriptor;
}
const cat = new Cat();
cat.meow();
When I try to execute this against the Node.js interpreter (version 9.1.0), I get the following error.
/Users/ravindranath/projects/decorators/index.js:2 #logger ^
SyntaxError: Invalid or unexpected token
at createScript (vm.js:80:10)
at Object.runInThisContext (vm.js:152:10)
at Module._compile (module.js:605:28)
at Object.Module._extensions..js (module.js:652:10)
at Module.load (module.js:560:32)
at tryModuleLoad (module.js:503:12)
at Function.Module._load (module.js:495:3)
at Function.Module.runMain (module.js:682:10)
at startup (bootstrap_node.js:191:16)
at bootstrap_node.js:613:3
So, my questions are:
Does Node.js 9.x support decorator syntax? Or is it coming up in some future version?
I see some express-js based decorators on GitHub, but I am unable to figure out how to create my own decorator. Can someone provide a simple basic example of creating a custom decorator with Node.js?

Decorators are not part of ECMAScript 2016 (aka 7). Decorators are currently in Stage 2 Draft out of the total 4 stages a feature goes through before being finalized and becoming part of the language. They'll probably be integrated into the language in the near future, but its features and specifics are subject to change. Because of this, you'll have to use a transpiler such as Babel to transform the decorators into code the Node runtime can understand (ECMAScript 2016) by installing the transform-decorators Babel plugin.
As for creating decorators, you've already done so. Each decorator is just a function that wraps another, that is provided with arguments based on the usecase, in your case target, key, and descriptor. Your logger function:
function logger(target, key, descriptor) {
console.log("Cat snarling...");
return descriptor;
}
Is already a decorator. For class properties and methods, target refers to the class of the property, key is the property name, and descriptor is the descriptor of the property. The decorator is then called and the property of the class is defined via Object.defineProperty once desugared. Your example can be boiled down to this:
class Cat { }
let meowDescriptor = {
type: 'method',
initializer: () => () => {
console.log(' Meeeoow! ');
},
enumerable: false,
configurable: true,
writable: true
}
function logger(target, key, descriptor) {
console.log("Cat snarling...");
return descriptor;
}
meowDescriptor = logger(Cat.prototype, 'meow', meowDescriptor);
Object.defineProperty(Cat.prototype, 'meow', {
...meowDescriptor,
value: meowDescriptor.initializer()
});
For classes themselves, decorators take one argument, target which describes the decorated class. I suggest reading some documentation on the subject to become acquainted with it.

Related

How to truncate custom field in (Node)JS error object when console-logging

I have a (Node)JS class:
class PayloadContainingError extends Error {
constructor(msg, payload) {
super(msg);
this.payload = payload;
}
}
payload field may contain long strings, even in MB range.
If I console.log this class at some point, I get the full payload in the log.
Instead, I want it to log a truncated portion (like the Linux head command).
E.g. if I console.error("Bad payload", instance_of_PayloadContainingError), instead of getting
Bad payload { Error: BAD
at foo.bar
payload:
'a possibly million-character long line that pollutes my log'
}
I want console to log
Bad payload { Error: BAD
at foo.bar
payload:
'first 100 chars...'
}
Is this possible via some magic on the class/field level - without having to refactor any (existing and future) console.log calls?
[Edit]
For those voting to close this question in favor of "JavaScript toString() override": per my understanding, toString() is not the question here - console seems to do beyond what toString() usually does, when logging an error object (e.g. adding the stacktrace - which I don't want to reimplement anyway). (As mentioned in one of my comments, overriding toString() does not change the output anyway.)
It seems like node's console.* methods take into account only enumerable properties. Therefore you could make this property non-enumerable:
class PayloadContainingError extends Error {
constructor(msg, payload) {
super(msg);
Object.defineProperty(this, "payload", {
enumerable: false,
value: payload,
});
}
}
const e = new PayloadContainingError("test error", "a possibly million-character long line that pollutes my log");
console.error(e);
$ node ./test.js
PayloadContainingError: test error
at Object.<anonymous> (/home/slava-b/arc/fei-24447/frontend/projects/infratest/packages/tokenator-universal/test.js:43:11)
at Module._compile (internal/modules/cjs/loader.js:956:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:973:10)
at Module.load (internal/modules/cjs/loader.js:812:32)
at Function.Module._load (internal/modules/cjs/loader.js:724:14)
at Function.Module.runMain (internal/modules/cjs/loader.js:1025:10)
at internal/main/run_main_module.js:17:11

Node repl module not found

I am trying to write a simple node script with some classes.
I first define a class
export default class Checkout() {
constructor () {
console.log('checkout')
}
check() {
console.log('check')
}
}
Then I am trying to use it
>node
>repl
check = new Checkout()
Uncaught ReferenceError: Checkout is not defined
require('Checkout')
Uncaught Error: Cannot find module 'Checkout'
How can I solve this? I am coming from Ruby where the console is pretty straight forward.
Code in a file isn't included in any other scope outside of that file until you require or import the file. When requiring/importing, the name the object you exported from the file doesn't automatically get used, you still have to specify it.
However, you're currently mixing require (CommonJS module format) with export default (ECMAScript Module format). There is only very limited interoperability between these formats using dynamic import(), but it's not yet available in the Node REPL (open issue here). If you need to test your Checkout class in the REPL, you'd need to just switch to using CommonJS:
module.exports = class Checkout() {
constructor () {
console.log('checkout')
}
check() {
console.log('check')
}
}
Usage:
> Checkout = require('checkout.js')
> check = new Checkout()

Error while trying to serialize a Qlik Sense app into a JSON object

I am trying to serialize a Qlik Sense app (.qvf file) into a JSON object.
For that I am passing the .qvf file in the below code as directed here - https://github.com/mindspank/serializeapp
The main reason behind this exercise is to save the JSON in Gitlab for version control since we cannot save .qvf in Git for version control as it is a binary file.
var qsocks = require('qsocks')
var serializeapp = require('serializeapp')
qsocks.Connect()
.then(global => global.openDoc('Executive D:\Users\ddas7071\Documents\Qlik\Sense\Apps\NewDeb.qvf'))
.then(app => serializeapp(app))
.then(result => console.log(result))
serializeapp = require('serializeapp')
const enigma = require('enigma.js')
const WebSocket = require('ws')
enigma.getService('qix', {
schema: require(`./node_modules/enigma.js/schemas/qix/12.67.2.json`),
session: {
host: 'localhost',
port: 4848,
secure: false
},
createSocket: (url) => new WebSocket(url)
})
.then(qix => qix.global.openDoc('Executive
D:\Users\ddas7071\Documents\Qlik\Sense\Apps\NewDeb.qvf'))
.then(app => serializeapp(app))
.then(result => console.log(result))
But while running the code (in windows), I am running into the below problem -
D:\Users\ddas7071\Desktop\Novartis_TechnicalDetails\myfile.js:12
enigma.getAttribute('qix', {
^
TypeError: enigma.getAttribute is not a function
at Object. (D:\Users\ddas7071\Desktop\Novartis_TechnicalDetails\myfile.js:12:8)
[90m at Module._compile (internal/modules/cjs/loader.js:1158:30)[39m
[90m at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)[39m
[90m at Module.load (internal/modules/cjs/loader.js:1002:32)[39m
[90m at Function.Module._load (internal/modules/cjs/loader.js:901:14)[39m
[90m at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)[39m
[90m at internal/main/run_main_module.js:18:47[39m
Though I understand the error, but not sure how to resolve it.
Note - All the pre-requisites are being taken care of already.
qsocks is not supported from some time and serializeapp is using qsocks under the hood.
If you want to connect to Qlik Engine please the official package - enigma.js. But there is no JS package (as far as i know that can extract the objects information.
But ...
There is a CLI (official) that can do this for you (havent tested it myself) - corectl. Corectl have option to unbuild the app into separate json/yaml files which you can then put under version control
The description for the unbuild command
Extracts generic objects, dimensions, measures, variables, reload script and connections from an app in an engine into separate json and yaml files. In addition to the resources from the app a corectl.yml configuration file is generated that binds them all together. Passwords in the connection definitions can not be exported from the app and hence need to be handled manually. Generic Object trees (e.g. Qlik Sense sheets) are exported as a full property tree which means that child objects are found inside the parent´s json (the qChildren array).

Reference a global JS function in another file, in NodeJS

What I'm actually doing is writing a VS Code Extension, but since I'm new to Node I'm struggling with referencing one JS file from another.
//main.js (compiled from TypeScript)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("./Test.js");
console.log("hello");
t1();
and
//Test.js
function t1() {
console.log("t1");
}
They're both in the same folder. If I run it from VS Code, or from node directly, it doesn't work
PS E:\VSCodeTest> node src/main.js
hello
E:\VSCodeTest\src\main.js:5
t1();
^
ReferenceError: t1 is not defined
at Object.<anonymous> (E:\VSCodeTest\src\main.js:5:1)
at Module._compile (module.js:635:30)
at Object.Module._extensions..js (module.js:646:10)
at Module.load (module.js:554:32)
at tryModuleLoad (module.js:497:12)
at Function.Module._load (module.js:489:3)
at Function.Module.runMain (module.js:676:10)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:608:3
The VS Code project is actually TypeScript but I've distilled it down to the crux of the problem in the JS files.
I believe it should work based on
https://www.typescriptlang.org/docs/handbook/modules.html
Import a module for side-effects only Though not recommended practice,
some modules set up some global state that can be used by other
modules. These modules may not have any exports, or the consumer is
not interested in any of their exports. To import these modules, use:
import "./my-module.js";
How have I misunderstood that?
Change Test.js to this:
//Test.js
function t1() {
console.log("t1");
}
module.exports = t1;
And then do something more like this in main.js:
const t1 = require("./Test.js");
t1(); // prints "t1"
There's a lot of information about how modules work in the docs: https://nodejs.org/api/modules.html
Alternatively, if you want t1 to be a global, then assign it to global.t1 in Test.js:
//Test.js
global.t1 = function t1() {
console.log("t1");
};
I wouldn't recommend that if you can avoid it, though, for all the reasons people recommend avoiding globals when possible
Require doesn't work quite like that, but you're close -- if you want to use a function you've created to in another file, just add it to that file's exports.
/// test.js
exports.t1 = function() ...
// or
module.exports = {
t1: function() ...
}
Then you need to specifically save that off to use it
/// main.js
var t1 = require('./test.js').t1;
t1();
Global scoping doesn't work like it does in the browser, check out node's docs on it, or try a blog explaining it (I didn't write this and can't fully vouch)

Node 5.10 spread operator not working

According to the docs, the latest node (Node 5+) should support the spread operator by default, like so:
const newObj = {
...oldObj,
newProperty: 1
}
And I have node 5.10.1 installed (e.g. that's what 'node -v' tells me). But I am still getting this error:
c:\[myprojectfolder]>node index.js
c:\[myprojectfolder]\index.js:21
...oldObj,
^^^
SyntaxError: Unexpected token ...
at exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:387:25)
at Object.Module._extensions..js (module.js:422:10)
at Module.load (module.js:357:32)
at Function.Module._load (module.js:314:12)
at Function.Module.runMain (module.js:447:10)
at startup (node.js:146:18)
at node.js:404:3
What am I missing?
The array spread syntax is supported, but the object spread syntax is not - this is most likely due to it not being finalized as part of the ECMAScript spec yet (it was originally planned for inclusion in ES7/ES2016, but it got bumped back, if I recall correctly).
Your options in the meantime are either to use a transpiler (such as Babel, with the transform-object-rest-spread plugin), or if that feels like overkill, you can use the new built-in Object.assign function. The object spread syntax is effectively just syntax sugar around Object.assign - the example in your question could be expressed like so:
const newObj = Object.assign({}, oldObj, {
newProperty: 1
});
Note the empty object as the first argument; the properties from the rest of the passed objects get merged into it, with those furthest to the right of the function call taking priority. It may seem more intuitive to simply have oldObj as the first argument, but this doesn't have quite the same effect - it would mutate the existing oldObj as well as returning it. Having an empty object as the target leaves oldObj unchanged.
Update: As of Node 8.6, object spread syntax is supported.
Update 2: Object spread syntax has finally made its way through the proposal process, and will be part of the ES2018 spec.
What you tried to use is called object spread and is not part of the es2015 specification. Node only supports the regular spread in function calls and array literals. Unfortunately not even array destructuring is supported yet, although they link to the MDN page which describes both structuring and destructuring use of ....
You can use Object.assign instead:
const newObj = Object.assign({}, oldObj, {
newProperty: 1
});
That's works in Node.js 8.5.0.
Example:
var oldObj = {
oldProperty: 0
}
var newObj = {
...oldObj,
newProperty: 1
}
console.log('Old Object: ' + JSON.stringify(oldObj, null, ' '))
console.log('New Object: ' + JSON.stringify(newObj, null, ' '))
Output:
Old Object: {
"oldProperty": 0
}
New Object: {
"oldProperty": 0,
"newProperty": 1
}
Screenshot from IDE Debug Console:

Resources