I'm working on a Node.js module in which several classes have to be exported. To save time, I decided to make export with a cycle. This is a snippet:
const _erros = {
MyError1: {
// fields
},
MyError2: {
// fields
}
// other errors
}
class BaseError extends Error {
constructor (data) {
// things
}
}
module.exports = Object.keys(_errors)
.reduce((acc, className) => Object.assign(acc, {
[className]: class extends BaseError {
constructor (message) {
const params = Object.assign({}, _errors[className])
params.message = message || params.message
super(params)
}
}
}), { BaseError })
Although the export is well done, VS Code IntelliSense detects only BaseError. Look at this image:
As you can see only BaseError is detected: MyError1, MyError2 and all other errors are not showed by IntelliSense.
Is this the proper VS Code behavior or it's a bug? And regardless the answer, is there a way to let VS Code IntelliSense work with exports made with cycles?
Related
For some reason we're having a ton of trouble using classes/prototypes in react native... we're not sure if we're doing something wrong, if es6 isn't actually supported or what. How can we use classes in react native? Clearly we aren't doing something right.
What we've tried
Creating a function and adding prototypes to it and exporting at the bottom
Creating and exporting a class with a constructor
Importing with {} and without, and exporting with default
The errors
Db is not a constructor
_Db2.default is not a constructor
Cannot read property 'default' of undefined
_Db.Db is not a constructor
No matter what we've tried, we cannot import an object of our creation and instantiate it. Here is an example of the prototype we've set up in another stackoverflow post we made when trying to untangle the issue
Here is an example of how we're importing it in.
import Db from '../localstorage/db/Db';
//var Db = require('../localstorage/db/Db');
const db = new Db();
When using require, it seems like the import statement works and an attribute we assign in the constructor exists, but none of the other prototypes are in the object.
EDIT 1: Below is our class implementation. We are instantiating realm outside of the class because realm seems to crash when instantiated inside of a class as documented in this github issue.
const realm = new Realm({
schema: [Wallet, WalletAddress, WalletTransaction, Log, APIWallet, APITransaction, APIAccount, Configuration],
path: config.db_path
});
export default class Db extends Object {
constructor() {
super();
this.realm = realm;
logger(2, realm.path);
}
//https://realm.io/docs/javascript/latest/#to-many-relationships
doOneToMany(one, many) {
many.forEach(m => {
this.write(() => {
one.push(m);
});
});
}
query(model, filter) {
let results = this.realm.objects(model);
if (filter) {
return results.filtered(filter);
}
return results;
}
insert(model, options) {
if (options == undefined && model instanceof Realm.Object) {
this.write(() => {
realm.create(model);
});
} else {
this.write(() => {
realm.create(model, options);
});
}
}
update(obj, options) {
this.write(() => {
Object.keys(options).map((key, attribute) => {
obj[key] = attribute;
});
});
}
del(model, obj) {
this.write(() => {
realm.delete(obj);
});
}
write(func) {
try {
realm.write(func);
} catch (e) {
logger(0, e);
throw new Error('Db.js :: Write operation failed ::', e);
}
}
close() {
Realm.close();
}
}
//module.exports = { Db };
The answer was we had a circular dependency in a Logger.js file that required Db.js while Db.js required Logger for useful logging. Removing the circular dependency caused classes and all the other import issues to go away.
I created some custom Erros in my application and I wanted to check for them later using the constructor name. The problem is that when I extend Error in my classes, the constructor.name is always "Error", not the name I actually gave to it.
I was doing some tests and noticed that this happens with the Error class, but not with any other custom class I create.
e.g.:
class CustomClass {
constructor(msg) {}
}
class OtherClass extends CustomClass {
constructor(msg) {
super(msg);
}
class CustomError extends Error {
constructor(msg) {
super(msg);
}
}
const e = new CustomError("There was an error");
const otherClass = new OtherClass("This is a class");
console.log(otherClass.constructor.name); // "OtherClass" <- ok!
console.log(e.constructor.name); // "Error" <- not ok! expected "CustomError"
Does anyone knows why this happens?
I thought I could do something like:
class CustomError extends Error {
constructor(msg) {
super(msg);
}
getName() {
return "CustomError";
}
}
const e = new CustomError("There was an error");
if(e.getName() == "CustomError") {
// do stuff
}
But then I get: TypeError: e.getName is not a function
Does anyone know if there is a way to override the constructor name when extending Error?
Also, why can't I declare and call methods in my CustomError error class?
EDIT
Following #samanime suggestion, I updated my node version to 8.8.1 and was able to find a partial solution.
Changing the syntax a little bit I got:
const FileSystemException = module.exports = class FileSystemException extends Error {
constructor(msg) {
super(msg);
}
getName() {
return "FileSystemException";
}
}
const e = new FileSystemException("There was an error");
// Running node app.js
console.log(e.constructor.name); // "FileSystemException"
console.log(e.getName()); // "FileSystemException"
// Running babel-node app.js
console.log(e.constructor.name); // "Error"
console.log(e.getName()); // "TypeError: e.getName is not a function"
Still, it would be awesome if someone could make it work with babel so I can use import/export statements without having to wait for node v9.4 LTS.
Using:
node v8.8.1
babel-node v6.26.0 w/ "es2015" and "stage-0" presets
thanks!
It seems to work just fine in this simple example:
class CustomError extends Error {
constructor(msg) {
super(msg);
}
}
const error = new CustomError()
console.log(error.constructor.name);
console.log(error instanceof CustomError);
console.log(error instanceof Error);
That is running with native class support (in Chrome).
It is possible that it isn't an issue with your syntax, but an issue with your transpiler. You probably want to dig through the transpiled code itself and see if it is doing anything special with Error that it doesn't with normal classes.
The fact that your getName() example didn't work also seems to indicate something funky is going on. Your examples you've posted look good. Double-check the code you are trying to run actually matches them.
A simple solution could be as follow
class CustomError extends Error {
constructor(msg) {
super(msg);
}
get name(){
return 'CustomError'
}
}
const e = new CustomError("There was an error");
console.log(e.constructor.name); // "CustomError"
console.log(e.name); // "CustomError"
This will also change the error name in the stack trace so when you throw your custom error object you will see:
CustomError: some random stack trace
Instead of:
Error: some random stack trace
For more information about using 'get' in js classes/objects, you can take a look at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
Hope I am not too late to the party and that you find this helpful.
To make error.name correctly set, you can create a base class for all your custom errors.
class MyAbstractError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class NotFoundError extends MyAbstractError {}
class BadParameterError extends MyAbstractError {}
console.log( (new BadParameter("x")).name) // => "BadParameter"
console.log( (new NotFoundError ("x")).name) // => "NotFoundError"
Or you can simply add a name() getter in your base class that returns this.constructor.name
I find Karma js tests somewhat cumbersome to set up and write and find myself often ignoring writing tests because of this, so I wanted to know if there exist better alternatives.
Since I use typescript my dream scenario would be if I could write something like this:
module adder {
export function add(a, b){
return a + b;
}
}
[Tests]
assert.equal(4, adder.add(2, 2));
My tests are inline and would be run directly in my editor when changes in current file occur. Since typescript could easily remove tests from final output I could put my tests in the same file as my code (The closer the better in my opinion). Does any testing framework support this and if not what would be needed to support this scenario.
Just a pedantic note - Karma is a test runner, not a test framework. However, it can use Jasmine, Mocha, QUnit or roll-your-own test framework.
You might be able to use decorator syntax to accomplish this type of behaviour. Something like:
#TestSuite
class AdderTests {
#Test
test1() {
assert.equal(4, adder.add(2,2));
}
#Test
test2() {
assert.equal(0, adder.add(2,-2));
}
}
Having said this, though, the structure of the test code is very similar to Jasmine syntax:
describe('AdderTests', () => {
it('should add 2 and 2', () => {
expect(adder.add(2,2)).toBe(4);
}
});
Your ideal, i.e:
[Test]
assert.equal( ... )
Is not really possible using TypeScript decorators, or even generics. You are trying to apply an attribute to an arbitrary line of code. This would have to be a function for a start, in order to correctly use JavaScript scoping rules:
[Test]
test1() { assert.equal( ... ) };
I suppose that any framework which declares tests as ordinary functions or objects can be made to support inline tests. You could declare the tests in Jasmine syntax as in blorkfish's answer alongside the code being tested. However I don't think a framework is needed.
It's worth remembering that the top level statements in a Type/JavaScript file are executed when it's imported (unless it is a type only import). So you can add test functions or objects to a global list of tests. Once all modules have been imported you can then run the tests.
For example let's say we have lib/test.ts
interface Suites {
[index: string]: Tests;
};
interface Tests {
[index: string]: () => Promise<void> | void;
};
let suites: Suites = {};
export function suite(name: string)
{
const suite: Tests = {};
suites[name] = suite;
return suite;
}
export async function run(on_pass: (desc: string) => void,
on_fail: (desc: string, error: Error) => void)
{
for (let suite in suites) {
for (let test in suites[suite]) {
const desc = `${suite}: ${test}`;
try {
// this will run the tests in serial
await suites[suite][test]();
on_pass(desc);
} catch (e) {
on_fail(desc, e as Error);
}
}
}
}
Then we can define a test like so
import { strict as assert } from 'node:assert';
import { inspect } from 'node:util';
import { suite } from 'lib/test';
// Create a test suite/group
const test = suite('lib/google');
// Not important
export type DateTime = { dateTime: string, };
export type DateOnly = { date: string, };
export function json2date(dt: DateTime | DateOnly)
{
// left as an exercise to the reader
}
// Add the test to the suite
test['json2date'] = () => {
const dt = {
dateTime: '2023-01-02T14:42:25.861Z',
};
const d = {
date: '2026-01-01',
};
assert.deepEqual(json2date(dt), new Date(dt.dateTime));
assert.deepEqual(json2date(d), new Date(2026, 0, 1));
};
test['An async test'] = async () => {
// test some async code
};
Then we need to load and execute all the tests. I do this as part of the apps health checks. That is, when a GET request is made to /sys/health, some code like the following is run
import { ... } from 'lib/google';
import { run as run_tests } from 'lib/test';
...
app.get('/sys/health', async () => {
let passes: number = 0;
let fails: number = 0;
const on_pass = (desc: string) => {
passes++;
log.info(`Test PASS: ${desc}`);
};
const on_fail = (desc: string, err: Error) => {
fails++;
log.error(`Test FAIL: ${desc}: ${inspect(err)}`);
};
await run_tests(on_pass, on_fail);
log.info(`Test summary: ${passes} passes, ${fails} fails, ${passes + fails} in total`);
if (fails > 0)
// return 500 to cause health checks to fail
});
Similar code can be used on the client. Indeed if a user experiences a client side error you can automatically run the client side tests in the background of the error page. The tests that will get run will depend on what modules were included.
The downside to this is that the test code will get included alongside the ordinary code. This isn't an issue if you run your unit tests in production as health checks. Also most bundlers support 'defines' and dead code elimination, so you can remove them from production code (e.g. https://esbuild.github.io/api/#define).
So I've got a fairly large sails.js application and I'm planning to migrate the codebase to TypeScript over the next few months, I modified sails moduleloader to use ts-node (I will probably open a pull-request very soon) and that's all fine.
The issue is that I'm not very satisfied with the TypeScript that I'm coming up with and wanted to know if anybody had any suggestions.
In sails a hook looks something like this:
module.exports = function(sails) {
return {
initialize: function(next) {
return next();
}
};
};
The TypeScript I initially came up with was
class Hook {
constructor(public sails: any) {}
initialize(next: Function) {
return next();
}
}
export = function(sails: any) {
return new Hook(sails);
}
But I don't find the export function great and there is the problem that sails bind the scope and therefore this will cause some problems. The solution is to use the fat arrow but I'm not a big fan of the solution I came up with...
class Hook {
constructor(public sails: any) {}
initialize = (next: Function) => {
return next();
};
}
export = function(sails: any) {
return new Hook(sails);
}
Any suggestions?
Thanks
I'm not sure what you're exactly looking for, but if you don't want to go fully OOP with a class Hook, you can just as well settle for an interface Hook. That way, you can keep your original code and just add some types to it.
interface Hook {
initialize(next: Function);
}
export = function(sails: any) : Hook {
return {
initialize: function(next) {
return next();
}
}
}
I have created a TypeScript interface for my service results. Now I want to define a basic functionality for both my functions inside. The problem is I get an error:
The property 'ServiceResult' does not exist on value of type 'Support'.
I use WebStorm for development (VS2012 makes me nervous because on freezes by large projects - waiting for better integration:P).
Here's how I do it:
module Support {
export interface ServiceResult extends Object {
Error?: ServiceError;
Check?(): void;
GetErrorMessage?(): string;
}
}
Support.ServiceResult.prototype.Check = () => {
// (...)
};
Support.ServiceResult.prototype.GetErrorMessage = () => {
// (...)
};
I have also tried to move my prototypes into the module, but same error still... (of course I removed Support. prefix).
You can't prototype an interface because the compiled JavaScript does not emit anything related to the interface at all. The interface exists purely for compile-time use. Take a look at this:
This TypeScript:
interface IFoo {
getName();
}
class Foo implements IFoo {
getName() {
alert('foo!');
}
}
Compiles to this JavaScript:
var Foo = (function () {
function Foo() { }
Foo.prototype.getName = function () {
alert('foo!');
};
return Foo;
})();
There is no IFoo in the result, at all - which is why you are getting that error. Typically you wouldn't prototype an interface, you would prototype a class that implements your interface.
You don't even have to write the prototype yourself, just implementing the interface as a class is enough and the TypeScript compiler will add the prototype for you.
It looks like you are trying to add implementation to an interface - which isn't possible.
You can only add to a real implementation, for example a class. You may also decide to just add the implementation to the class definition rather than directly using prototype.
module Support {
export interface ServiceResult extends Object {
Error?: ServiceError;
Check?(): void;
GetErrorMessage?(): string;
}
export class ImplementationHere implements ServiceResult {
Check() {
}
GetErrorMessage() {
return '';
}
}
}
Support.ImplementationHere.prototype.Check = () => {
// (...)
};
Support.ImplementationHere.prototype.GetErrorMessage = () => {
// (...)
};