I am fairly new to Node.JS, but have some experience in other languages. I am trying to achieve the following:
I want to perform a task and if it fails perform another task.
I have two files: one is the main function, the other contains the class.
First the main function (main.js):
(async function main() {
let { MyClass } = require("./my_class.js");
const mc = new MyClass();
await mc.do_stuff();
console.log(mc.message);
})();
The other is the class (my_class.js)
class MyClass {
constructor() {
this.message='hello';
}
do_stuff=async function() {
return new Promise((resolve,reject) => async function (){
let [res,rej]=await do_first('fail');
if(rej) {
console.log('do_first() failed.');
[res,rej]=await do_second('succeed');
if(rej) {
console.log('do_second() failed.');
reject('failed');
} else {
console.log('do_second() succeeded.');
resolve('success');
}
} else {
console.log('do_first() succeeded, no call to do_second().');
resolve('success');
}
});
}
do_first=async function(param) {
return new Promise((resolve,reject) => {
if(param==='fail') {
console.log('rejecting do_first()');
reject('failure');
} else {
console.log('resolving do_first()');
resole('success');
}
});
}
do_second=async function(param) {
return new Promise((resolve,reject) => {
if(param==='fail') {
console.log('rejecting do_second()');
reject('failure');
} else {
console.log('resolving do_second()');
resole('success');
}
});
}
}
exports.MyClass = MyClass
If I try to run it with node ./main.js nothing happens. If I run mc.do_stuff() without the await, I do get the hello... Which boats am I missing?
For that matter: I am running NodeJS v18.12.0
A few things need to be changed here to make it work:
When using await, only the Promise.resolve comes back to regular code execution. Promise.reject always raises an Exception.
Typo resole
changed code to consistently use arrow syntax. This requires referencing functions as instance members -> this.do_first. I suppose this was your intention. The syntax before did not execute the function as part of the object, but in global scope.
Here's your code in a working state
class MyClass {
constructor() {
this.message = "hello";
}
do_stuff = async () => {
return new Promise(async (resolve, reject) => {
try {
await this.do_first("fail");
console.log("do_first() succeeded, no call to do_second().");
resolve("success");
} catch (err) {
console.log("do_first() failed.");
try {
await this.do_second("succeed");
console.log("do_second() succeeded.");
resolve("success");
} catch (err) {
console.log("do_second() failed.");
reject("failed");
}
}
});
};
do_first = async (param) => {
return new Promise(async (resolve, reject) => {
if (param === "fail") {
console.log("rejecting do_first()");
reject("failure");
} else {
console.log("resolving do_first()");
resolve("success");
}
});
};
do_second = async (param) => {
return new Promise((resolve, reject) => {
if (param === "fail") {
console.log("rejecting do_second()");
reject("failure");
} else {
console.log("resolving do_second()");
resolve("success");
}
});
};
}
exports.MyClass = MyClass;
PS C:\Users\patrick\Documents\GitHub\stackoverflow-74714360> node ./main.js
rejecting do_first()
do_first() failed.
resolving do_second()
do_second() succeeded.
hello
We have module within there is some initial logic which test that some value was configured, if not, it throws error.. then also the module provides methods. I want to describe this in specification (test), using Jest framework and test the feature. Here is simplified reproduced example:
// dependency.service.ts
export const something = {
method() {
return "methodValue";
}
};
export default function somethingElse() {
return "somethingElseValue";
}
// index.ts
import somethingElse, { something } from "./dependency.service";
const value1 = somethingElse();
const value2 = something.method();
console.log("ROOT somethingElse", value1);
console.log("ROOT something.method", value2);
// initialisation of module fails
if(value1 === 'throw_error' || value2 === 'throw_error') {
throw 'Some error';
}
export function smElse() {
const value = somethingElse();
console.log("somethingElse", value);
return value;
}
export function smMethod() {
const value = something.method();
console.log("somethingElse", value);
return value;
}
// index.spec.ts
import { smElse, smMethod } from './index';
jest.mock('./dependency.service', () => ({
__esModule: true,
default: jest.fn(() => 'MOCKED_somethingElseValue'),
something: {
method: jest.fn(() => 'MOCKED_methodValue'),
},
}));
describe('index', () => {
// some tests for happy paths
it('smElse returns mocked value', () => {
expect(smElse()).toMatchInlineSnapshot(`"MOCKED_somethingElseValue"`);
});
it('smMethod returns mocked value', () => {
expect(smMethod()).toMatchInlineSnapshot(`"MOCKED_methodValue"`);
});
it('smElse returns per test mocked value', () => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('ANOTHER_MOCKED_somethingElseValue');
expect(smElse()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_somethingElseValue"`);
});
it('smMethod returns per test mocked value', () => {
const something = require('./dependency.service').something;
something.method.mockReturnValueOnce('ANOTHER_MOCKED_methodValue');
expect(smMethod()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_methodValue"`);
});
// this is testing the throwing error in module root
it('throws error when somethingElse returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
it('throws error when something.method returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingMethodMocked = require('./dependency.service').something.method;
somethingMethodMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
});
"Try catch in isolation" solution does not work with async code as isolateModules method does not support async functions yet, reference: https://github.com/facebook/jest/issues/10428. I need alternative solution which would support async code.
Whole reproduced example repo here: https://github.com/luckylooke/jestTestModuleRootThrow/tree/main
EDIT:
I found out that some asynchronicity is supported by isolateModules, at least my use case, once I used expect.assertions(1) following test works as expected:
it('throws error when token data are not valid', async () => {
expect.assertions(1);
jest.isolateModules(async () => {
require('crypto-package').someMethod.mockReturnValueOnce(Promise.resolve({
decoderMethod: () => Promise.resolve('{ broken data }'),
}));
const { getTokenData } = require("./decoder.service");
await expect(getTokenData('34534xxxxxxxxxxxxxxx12628')).rejects.toMatchInlineSnapshot(`"Invalid token data"`);
});
});
I used async-lock module for my typescript program of concurrency.
As I use this, I want to return the result in lock.acquire(...) {...}, but it's not working well.
How can I return the value? I'll be grateful for any advice about this. Thanks!
public async getValue(key: string): Promise<any> {
const base = this;
lock.acquire(key, async function (done) {
base.logger.info(`${key} lock enter`);
if (!await base.myRepository.checkDBTable(key)) {
const valueFromNetwork: number = await base.getValueFromNetwork(key);
const initResult: MyEntity = await base.myRepository.initNonce(key, valueFromNetwork);
if (!initResult) {
throw new Error('initValue failed...');
}
base.logger.debug(JSON.stringify(initResult, null, 4));
}
const valueFromDB: number = await base.myRepository.getValueFromDB(key);
if (valueFromDB === -1 || valueFromDB === undefined) {
throw new Error('getValueFromDB failed...');
} else {
const updateResult: MyEntity = await base.myRepository.updateValue(key, valueFromDB);
if (!updateResult) {
throw new Error('updateValue failed...');
}
base.logger.info(`${valueFromDB}`);
base.logger.info(`${key} lock done`);
done();
}
// I'd like to return valueFromDB above.
});
}
You have two options:
You can implement a wrapper around lock.aquire (Recommended as it is a little easier to read and can handle acquisition errors):
public async getValue(key: string): Promise<any> {
const base = this;
const done = await this.aquireLock(key);
base.logger.info(`${key} lock enter`);
if (!await base.myRepository.checkDBTable(key)) {
const valueFromNetwork: number = await base.getValueFromNetwork(key);
const initResult: MyEntity = await base.myRepository.initNonce(key, valueFromNetwork);
if (!initResult) {
throw new Error('initValue failed...');
}
base.logger.debug(JSON.stringify(initResult, null, 4));
}
const valueFromDB: number = await base.myRepository.getValueFromDB(key);
if (valueFromDB === -1 || valueFromDB === undefined) {
throw new Error('getValueFromDB failed...');
} else {
const updateResult: MyEntity = await base.myRepository.updateValue(key, valueFromDB);
if (!updateResult) {
throw new Error('updateValue failed...');
}
base.logger.info(`${valueFromDB}`);
base.logger.info(`${key} lock done`);
done();
}
return valueFromDB;
}
private async aquireLock(key: string): Promise<() => void> {
return new Promise((resolve, reject) => {
lock.acquire(key, done => {
resolve(done);
}, (err)=>{ // in case our aquire fails(times out, etc.)
if(err){
reject(err);
}
})
})
}
Playground
Or, you can use a function constructor (even if it is an anti-pattern):
getValue(key: string): Promise<any> {
return new Promise((resolve, reject) => {
const base = this;
lock.acquire(key, async function (done) {
base.logger.info(`${key} lock enter`);
if (!await base.myRepository.checkDBTable(key)) {
const valueFromNetwork: number = await base.getValueFromNetwork(key);
const initResult: MyEntity = await base.myRepository.initNonce(key, valueFromNetwork);
if (!initResult) {
reject(new Error('initValue failed...'));
}
base.logger.debug(JSON.stringify(initResult, null, 4));
}
const valueFromDB: number = await base.myRepository.getValueFromDB(key);
if (valueFromDB === -1 || valueFromDB === undefined) {
reject(new Error('getValueFromDB failed...'));
} else {
const updateResult: MyEntity = await base.myRepository.updateValue(key, valueFromDB);
if (!updateResult) {
reject(new Error('updateValue failed...'));
}
base.logger.info(`${valueFromDB}`);
base.logger.info(`${key} lock done`);
done();
}
resolve(valueFromDB)
});
})
}
Playground (Many things stubbed out, but it gets the general idea across)
I am trying to create a simple event emitter, and when I attempt to test the emitter it is showing up undefined through jest.
Code for the Event Emitter:
class EventEmitter {
constructor() {
this.events = {};
}
on(e, fn) {
if (this.events[e]) {
return this.event[e].add(fn);
}
return this.events[e] = new Set([fn]);
}
once(e, fn) {
if (this.events[e]) {
this.event[e].add(fn, 1);
}
this.events[e] = new Set([fn, 1]);
}
emit(e, ...args) {
if (this.events[e]) {
this.events[e].forEach(fn => {
if (typeof fn === 'function')
fn.apply(null, args);
});
if (this.events[e].has(1)) {
delete this.events[e];
}
} else if (!this.events[e]) {
console.log('Event Argument does not exist - please enter a valid event');
}
}
off(e) {
if (arguments.length === 0) {
this.events = {};
}
if (this.events[e]) {
delete this.events[e];
} else if (!this.events[e]) {
return console.log('Event Argument does not exist - please enter a valid event');
}
}
}
Code for the testing:
const EventEmitter = require('../index.js');
// Creating new Event Emitter
test('should Emit named events with a single argument', () => {
const ee = new EventEmitter();
ee.on('multiple', (x, y) => {
console.log(`First Argument: ${x}, Second Argument: ${y}`);
});
ee.on('single', () => "Why isnt this working")
const res = console.log(ee.emit('single'))
expect(res).toBe('Why isnt this working')
})
const res is returning undefined, but i am not sure why - any help would be so appreciated!
In my concurrent project, I need to get a value in redis then update it and set in redis. Like the following code, the result I expected should be 3000, but I can't get the correct result. The sequence maybe wrong, may GET GET SET SET or GET SET SET GET, etc. How can I get the right sequence and correct result? Should I use some lock?
import * as redis from 'redis';
let config: redis.ClientOpts = {
host: '127.0.0.1',
port: 6379
};
let redisClient: redis.RedisClient = new redis.RedisClient(config);
redisClient.set('num', '0');
(async () => {
for (let i = 0; i < 1000; i++) {
await add ();
}
})();
(async () => {
for (let i = 0; i < 1000; i++) {
await add ();
}
})();
(async () => {
for (let i = 0; i < 1000; i++) {
await add ();
}
})();
// I know incr command, this is just an example.
async function add () {
let numStr: string = await get('num');
let num: number = Number(numStr);
num++;
await set('num', String(num));
console.log(num);
}
async function get (key: string): Promise<string> {
return new Promise<string>((resovle, reject) => {
redisClient.get(key, (err: Error, reply: string) => {
if (err) {
console.error(err);
reject(err);
}
resovle(reply);
})
});
}
async function set (key: string, value: string): Promise<string> {
return new Promise<string>((resovle, reject) => {
redisClient.set(key, value, (err: Error, reply: string) => {
if (err) {
console.error(err);
reject(err);
}
resovle(reply);
})
});
}
Redis provides a more flexible way to 'atomically' perform transactions using the MULTI-EXEC approach.
Imagine you're working on a banking system, and a customer requests for a monet transfer. You need to first discount the money from the client's account and then deposit it into the other account. In between these operations, one of them might fail. So, we use transactions.
A transaction is atomic, meaning that either all of the operations happen all none of them do. Databases have locking and concurrency management implementation that ensures this occurs, whiles it does not affect the system's performance drastically.
Example:
var redis = require("redis"),
client = redis.createClient(), multi;
async function add () {
multi = client.multi();
let numStr: string = await multi.get('num');
let num: number = Number(numStr);
num++;
await multi.set('num', String(num));
multi.exec(function (err, replies) {
console.log(replies);});
}
}
I've solved it, thanks everyone. Here is my code. ref: https://redis.io/topics/distlock
import * as redis from 'redis';
import * as crypto from 'crypto';
const lockScript: string = 'return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])';
const unlockScript: string = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end';
let config: redis.ClientOpts = {
host: '127.0.0.1',
port: 6379
};
let redisClient: redis.RedisClient = new redis.RedisClient(config);
redisClient.set('num', '0');
(async () => {
for (let i = 0; i < 1000; i++) {
await add ();
}
})();
(async () => {
for (let i = 0; i < 1000; i++) {
await add ();
}
})();
(async () => {
for (let i = 0; i < 1000; i++) {
add ();
}
})();
async function add () {
let resourse: string = 'lock:num';
let uniqueStr: string = crypto.randomBytes(10).toString('hex');
await attemptLock(resourse, uniqueStr);
let num: number = Number(await get('num'));
num++;
console.log(num);
await set('num', String(num));
await unlock(resourse, uniqueStr);
}
async function loop (resourse: string, uniqueStr: string, ttl?: string) {
return new Promise<any>((resolve, reject) => {
setTimeout(async () => {
let result: any = await lock(resourse, uniqueStr, ttl);
resolve(!!result);
}, 10);
});
}
async function attemptLock (resourse: string, uniqueStr: string, ttl?: string) {
let result = await loop(resourse, uniqueStr, ttl);
if (result) {
return true;
} else {
return attemptLock(resourse, uniqueStr, ttl);
}
}
async function lock (resourse: string, uniqueStr: string, ttl?: string) {
ttl = ttl ? ttl : '30000';
return new Promise<any>((resolve, reject) => {
redisClient.eval(lockScript, 1, resourse, uniqueStr, ttl, (err: Error, reply: any) =>{
if (err) {
console.error(err);
reject(err);
}
// reply will be nil when the key exists, on the contrary it will be "OK"
resolve(reply);
});
});
}
async function unlock (resourse: string, uniqueStr: string) {
return new Promise<any>((resolve, reject) => {
redisClient.eval(unlockScript, 1, resourse, uniqueStr, (err: Error, reply: any) =>{
if (err) {
console.error(err);
reject(err);
}
resolve(reply);
});
});
}
async function get (key: string): Promise<string> {
return new Promise<string>((resovle, reject) => {
redisClient.get(key, (err: Error, reply: string) => {
if (err) {
console.error(err);
reject(err);
}
resovle(reply);
})
});
}
async function set (key: string, value: string): Promise<string> {
return new Promise<string>((resovle, reject) => {
redisClient.set(key, value, (err: Error, reply: string) => {
if (err) {
console.error(err);
reject(err);
}
resovle(reply);
})
});
}
What you need to solve your problem is to execute all the actions (get, increment, set) performed by the add() function inside a transaction in order to achieve atomicity - this can be achieved in Redis with the following ways:
Redis transactions (MULTI/EXEC/WATCH), with optimistic locking (retries)
Redis Lua script: create a lua script that will perform the add function and execute it
You are facing this problem because you are not waiting for each IFFE function to finish. Hence they run in parallel and will not give you the result you are looking for.
To solve this, you need to await on each of the IFFE function. Now we can await only if the function in async. So I have converted the IFFE function into normal function. Here is the implementation
async function run() {
for (let i = 0; i < 1000; i++) {
await add();
}
}
// I know incr command, this is just an example.
async function add() {
let numStr = await get('num');
let num = Number(numStr);
num++;
await set('num', String(num));
console.log(num);
}
function get(key) {
return new Promise((resovle, reject) => {
redisClient.get(key, (err, reply) => {
if (err) {
console.error(err);
reject(err);
}
resovle(reply);
});
});
}
async function set(key, value) {
return new Promise((resovle, reject) => {
redisClient.set(key, value, (err, reply) => {
if (err) {
console.error(err);
reject(err);
}
resovle(reply);
})
});
}
async function runner() {
await run();
await run();
await run();
}
runner();