Singleton Object for Dynamically Imported Module in Next.js - javascript

I'm trying to have a Singleton instance of a dynamically imported module in my Next.js app. However, the current implementation I have seems to initialize a new instance every time I call getInstance.
Here's the implementation in gui.js:
let dynamicallyImportPackage = async () => {
let GUI;
await import('three/examples/jsm/libs/dat.gui.module.js')
.then(module => {
GUI = module.GUI
})
.catch(e => console.log(e))
return GUI;
}
let GUI = (function () {
let instance;
return {
getInstance: async () => {
if (!instance) {
let GUIModule = await dynamicallyImportPackage();
instance = new GUIModule();
}
return instance;
}
};
})();
export default GUI;
and I call it in an ES6 class function using GUI.getInstance().then(g => { ... })
I would normally use React Context API or Redux for this kind of shared state but for this case, I need it to be purely in ES6 JS and not React.

You need to cache the promise, not the instance, otherwise it will try to import the module (and instantiate another instance) again while the first is still loading and has not yet assigned the instance variable.
async function createInstance() {
const module = await import('three/examples/jsm/libs/dat.gui.module.js')
const { GUI } = module;
return new GUI();
}
let instancePromise = null;
export default function getInstance() {
if (!instancePromise) {
instancePromise = createInstance()
// .catch(e => {
// console.log(e); return ???;
// instancePromise = null; throw e;
// });
}
return instancePromise;
}

Related

How to execute asynchronous functions in the constructor? [duplicate]

This question already has an answer here:
Asynchronous operations in constructor
(1 answer)
Closed 3 years ago.
I am trying to implement the Customer object in NodeJs and that the instance is able to collect its data.
class CustomerModel extends Model {
public customer
constructor(email:string) {
super();
this.collection = 'customer';
this.customer = await CustomerLayer.getCustomerByEmail(email);
}
}
But I can't have an asynchronous constructor. I have seen that in Javascript you can do the following:
const sleep = () => new Promise(resolve => setTimeout(resolve, 5000));
class Example {
constructor () {
return new Promise(async (resolve, reject) => {
try {
await sleep(1000);
this.value = 23;
} catch (ex) {
return reject(ex);
}
resolve(this);
});
}
}
(async () => {
// It works as expected, as long as you use the await keyword.
const example = await new Example();
console.log(example instanceof Example);
console.log(example.value);
})();
But I think it is not correct to return the data from the constructor.
Is there any correct way to call asynchronous methods from the constructor?
I wouldn't do it in constructor. Probably init method fits this use case better.
class CustomerModel extends Model {
public customer
constructor(email:string) {
super();
this.collection = 'customer';
}
async init() {
this.customer = await CustomerLayer.getCustomerByEmail(email);
}
}
const myClass = new CustomerModel();
await myClass.init();
You could also consider creating a static method to return the instance which internally create object and init.
It's not possible. You have a few options here:
You can explicitly return a Promise from the constructor (construct it via new Promise). This is what you're doing in your second code. await can always be substituted with the Promise constructor and .then. But, it's pretty weird, since one would always expect the result of new Example() to then be an instanceof Example - one wouldn't expect new Example() to result in a Promise. This is what your first code would look like using this method:
class CustomerModel extends Model {
public customer
constructor(email:string) {
super();
this.collection = 'customer';
return CustomerLayer.getCustomerByEmail(email)
.then((customerByEmailResolveValue) => {
this.customerByEmailResolveValue = customerByEmailResolveValue;
return this;
});
}
}
const customerProm = new CustomerModel('foo');
customerProm.then((actualCustomerInstance) => {
// use actualCustomerInstance
});
In the constructor, assign the Promise to a property of the instance. Then, when you need to consume that value, call .then on the property:
class CustomerModel extends Model {
public customer
constructor(email:string) {
super();
this.collection = 'customer';
this.customerProm = CustomerLayer.getCustomerByEmail(email);
}
}
const customer = new CustomerModel('foo');
customer.customerProm.then((result) => {
// use result;
});
You can also retrieve the value before creating the instance, then pass it in:
class CustomerModel extends Model {
public customer
constructor(email:string, customerByEmailResolveValue) {
super();
this.collection = 'customer';
this.customer = customerByEmailResolveValue;
}
}
CustomerLayer.getCustomerByEmail(email)
.then((customerByEmailResolveValue) => {
const customer = new CustomerModel('foo', customerByEmailResolveValue);
});

How to mock a node module with jest within a class?

I need to mock the DNS node module in a class but I am unsure how to do so as it is enclosed in the class. Here is a sample of what the class looks like...
import { lookup } from 'dns';
class Foo {
// ...
protected async _bar(IP: string) {
// I want to mock "lookup"
await new Promise<undefined>((resolve, reject) => {
lookup(IP, (err, addr) => {
if (err) reject(new Error('DNS Lookup failed for IP_ADDR ' + IP));
resolve();
});
});
// If dns found then return true
return true;
}
// ...
}
I would like to create a test file foo.spec.ts that contains a test similar to the following:
import { Foo } from './Foo';
describe('Foo', () => {
it('Bar Method returns true on success', () => {
const test = new Foo();
expect(test._bar('192.168.1.1')).resolves.toBeTruthy();
});
});
I am unsure how to mock the lookup call within the class Foo given that the class definition is in a separate file from the test itself.
Any help would be appreciated!
The way you are using lookup won't work since it doesn't return a Promise...
...but you can convert it to a version that does return a Promise by using util.promisify.
The code would end up looking something like this:
import { lookup as originalLookup } from 'dns'; // <= import original lookup...
import { promisify } from 'util';
const lookup = promisify(originalLookup); // <= ...and promisify it
export class Foo {
async _bar(IP: string) {
await lookup(IP).catch(err => { throw new Error('Failed'); });
return true;
}
}
You could then mock lookup in your test using jest.mock like this:
import { Foo } from './Foo';
jest.mock('dns', () => ({
lookup: (hostname, callback) => {
hostname === 'example.com' ? callback() : callback('error');
}
}))
describe('Foo', () => {
it('Bar Method returns true on success', async () => {
const test = new Foo();
await expect(test._bar('example.com')).resolves.toBeTruthy(); // Success!
await expect(test._bar('something else')).rejects.toThrowError('Failed'); // Success!
});
});
Note that the mock needs to be created using jest.mock (and not something like jest.spyOn) since calls to jest.mock get hoisted and run first. The mock needs to be in place before Foo.js is imported since the first thing it does is create and store the promisified lookup.
From jest's tutorial,
jest.mock('./sound-player', () => {
return function() {
return {playSoundFile: () => {}};
};
});
so you could do sth like jest.mock('dns', () => ({ ... }));

Jest: restore original module implementation on a manual mock

I have a pretty common testing use case and I am not sure what's the best approach there.
Context
I would like to test a module that depends on a userland dependency. The userland dependency (neat-csv) exports a single function that returns a Promise.
Goal
I want to mock neat-csv's behavior so that it rejects with an error for one single test. Then I want to restore the original module implementation.
AFAIK, I can't use jest.spyOn here as the module exports a single function.
So I thought using manual mocks was appropriated and it works. However I can't figure it out how to restore the original implementation over a manual mock.
Simplified example
For simplicity here's a stripped down version of the module I am trying to test:
'use strict';
const neatCsv = require('neat-csv');
async function convertCsvToJson(apiResponse) {
try {
const result = await neatCsv(apiResponse.body, {
separator: ';'
});
return result;
} catch (parseError) {
throw parseError;
}
}
module.exports = {
convertCsvToJson
};
And here's an attempt of testing that fails on the second test (non mocked version):
'use strict';
let neatCsv = require('neat-csv');
let { convertCsvToJson } = require('./module-under-test.js');
jest.mock('neat-csv', () =>
jest.fn().mockRejectedValueOnce(new Error('Error while parsing'))
);
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = (promise) =>
promise.then(
(value) => {
throw value;
},
(reason) => reason
);
test('mocked version', async () => {
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
});
test('non mocked version', async () => {
jest.resetModules();
neatCsv = require('neat-csv');
({ convertCsvToJson } = require('./module-under-test.js'));
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual(
'[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]'
);
});
I am wondering if jest is designed to do such things or if I am going the wrong way and should inject neat-csv instead ?
What would be the idiomatic way of handling this ?
Yes, Jest is designed to do such things.
The API method you are looking for is jest.doMock. It provides a way of mocking modules without the implicit hoisting that happens with jest.mock, allowing you to mock in the scope of tests.
Here is a working example of your test code that shows this:
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = promise =>
promise.then(value => {
throw value;
}, reason => reason);
test('mocked version', async () => {
jest.doMock('neat-csv', () => jest.fn().mockRejectedValueOnce(new Error('Error while parsing')));
const neatCsv = require('neat-csv');
const { convertCsvToJson } = require('./module-under-test.js');
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
jest.restoreAllMocks();
});
test('non mocked version', async () => {
const { convertCsvToJson } = require('./module-under-test.js');
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual('[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]');
});

Async/await in ES6 class: await will not resolve and stays a Promise

My await in initalize does not get called and I don't know why:
I have a React class that acts as the entry point:
export const HulaHoop = React.createClass({
async parseData(objects){
return await parser(objects)
}
});
which calls the parser function that creates instances of MyClass:
class MyClass {
constructor(name){
this.name = name;
}
async initialize(obj){
this.field = await getField(obj);
console.log(this.field) // <--- <Promise>
}
static async create(obj) {
const myInstance = new MyClass(obj);
await myInstance.initialize(obj);
return myInstance;
}
}
export async function getField(obj) {
const d2 = await getInstance();
let route = 'someField/' + obj.field;
return await d2.get(route);
}
export default async function parser(objects) {
let classes = [];
for (let obj of objects) {
let myclass = await MyClass.create(obj)
classes.push(myclass)
}
return classes
}
Since the (final) class creation happens asynchronously, there is a static async create() method in place to initialize the Class. Initalize can never finish because it's waiting for the Async callback. How can I resolve so that this.field is actually resolved?
Edit: it's the Jest testing that fails:
test('parsing', async () => {
let objects = await readCSV('file.csv');
let res = await parser(objects); // waits forever in MyClass.create(obj)
const expected = [ ... ]
expect(data).toEqual(res);
}

Javascript set const variable inside of a try block

Is it possible in ES6 to set a variable inside of a try{} using const in strict mode?
'use strict';
const path = require('path');
try {
const configPath = path.resolve(process.cwd(), config);
} catch(error) {
//.....
}
console.log(configPath);
This fails to lint because configPath is defined out of scope. The only way this seems to work is by doing:
'use strict';
const path = require('path');
let configPath;
try {
configPath = path.resolve(process.cwd(), config);
} catch(error) {
//.....
}
console.log(configPath);
Basically, is there any way to use const instead of let for this case?
Declaring a variable as const requires you to immediately point it to a value and this reference cannot be changed.
Meaning you cannot define it at one place (outside of try) and assign it a value somewhere else (inside of try).
const test; // Syntax Error
try {
test = 5;
} catch(err) {}
On the other hand, both creating it and giving it a value within the try block is fine.
try {
const test = 5; // this is fine
} catch(err) {}
However, const is block-scoped, like let, so if you do create it and give it a value within your try block, it will only exist within that scope.
try {
const test = 5; // this is fine
} catch(err) {}
console.log(test); // test doesn't exist here
Therefore, if you need to access this variable outside of the try, you must use let:
let configPath;
try {
configPath = path.resolve(process.cwd(), config);
} catch(error) {
//.....
}
console.log(configPath);
Alternatively, although probably more confusingly, you can use var to create a variable within the try and use it outside of it because var is scoped within the function, not the block (and gets hoisted):
try {
var configPath = path.resolve(process.cwd(), config);
} catch(error) {
//.....
}
console.log(configPath);
'use strict';
const path = require('path');
const configPath = (function() {
try {
return path.resolve(process.cwd(), config);
} catch (error) {
//.....
}
})()
console.log(configPath);
I would try to use a temp variable with let and assign that to a const var after the try/catch and 'delete' the temp var.
'use strict';
let temp;
try {
temp = path.resolve(process.cwd(), config);
} catch (error) {
//.....
}
const configPath = temp;
temp = undefined;
console.log(configPath);
There are plenty of good answers here. But it's real annoying to have to manage your lets being inside and out of scope. So if you are like me, and came here searching for a better pattern. I wrote a little utility that helps a ton.
export const tc = <T>(option: { try: () => T; catch: (e: Error) => T }) => {
try {
return option.try()
} catch (e) {
return option.catch(e)
}
}
Here are some unit tests to show how it's useful
import { tc } from './tc'
describe('tc', () => {
it('should return successfully', () => {
const result = tc({
try: () => 1 + 2,
catch: () => -1,
})
expect(result).toEqual(3)
})
it('should catch', () => {
const spy = jest.fn()
const result = tc({
try: (): number => {
throw new Error('Test')
},
catch: (e) => {
spy()
expect(e.message).toEqual('Test')
return -1
},
})
expect(spy).toHaveBeenCalledTimes(1)
expect(result)
})
it('should rethrow', () => {
expect(() =>
tc({
try: (): number => {
throw new Error('Test')
},
catch: (e) => {
throw e
},
}),
).toThrowError()
})
it('should have proper types', () => {
// #ts-expect-error
const result: string = tc({
try: () => 12,
catch: (e) => {
return -1
},
})
})
})
You can avoid the try block altogether if the function is async! Just learned this today, thought I'd share!
More broadly applicable to just your situation as this is the top Google result for "const in a try block"
'use strict';
const path = require('path');
const getPath = async () => {
return path.resolve(process.cwd())
}
const logPath = async () => {
const configPath = await getPath().catch(e => console.log(e)) <--avoid the try
console.log(configPath);
}
logPath()
Works great when you're already in an async function and need to wait for something else:
async function main() {
const result = await asyncTask().catch(error => console.error(error));
}
Learned from: https://stackoverflow.com/a/55024396/2936521
Besides the let options I see here, another option may be to use an object, as the reference to the object is constant, but it's properties can be altered, so something like this:
'use strict';
const path = require('path');
const configPath = { value: null };
try {
configPath.value = path.resolve(process.cwd(), config);
} catch(error) {
//.....
}
console.log(configPath.value);
It would probably be cleaner to stick with let, but I just wanted to point out another possible option.
You can just do:
const result = await promise.catch(err=>{
console.log(err)
})
Use let. You cannot use const. const does not allow you to reassign the declared constant. While it is generally good practice to declare objects like yours with const, the entire point of doing so is to allow objects to be mutated without allowing them to be reassigned. You are reassigning the object (thus, defeating the purpose of const), so use let instead.
let path = require('path');
// Good to go!

Categories

Resources