Javascript set const variable inside of a try block - javascript

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!

Related

Why are my variables undefined at runtime?

I have this code in my action creator:
const getMyFilenames = async () => {
const response = await axios.get(API_URL, {
withCredentials: true,
});
const rawFilenames = response.data;
const filenames = rawFilenames.map((filename) => filename.split("-"));
for (filename in filenames) {
filenames[filename].shift();
filenames[filename] = filenames[filename].join("-");
}
return { filenames, rawFilenames };
};
I don't understand why the 'filename' constant calls are showing as undefined at runtime.
I assume it must be something to do with the asynchronous code at the beginning of the function, but I'm not sure...
Many thanks
for in loops need to have the iterator initialised with const, in React.
for (const filename in filenames) {
is the solution

Javascript issue when using let

I have the below js for unit testing an error handler:
import assert from 'assert';
import deepClone from 'lodash.clonedeep';
import deepEqual from 'lodash.isequal';
import { spy } from 'sinon';
import errorHandler from './index';
function getValidError(constructor = SyntaxError) {
let error = new constructor();
error.status = 400;
error.body = {};
error.type = 'entity.parse.failed';
return error;
}
describe('errorHandler', function() {
let err;
let req;
let res;
let next;
let clonedRes;
describe('When the error is not an instance of SyntaxError', function() {
err = getValidError(Error);
req = {};
res = {};
next = spy();
clonedRes = deepClone(res);
errorHandler(err, req, res, next);
it('should not modify res', function() {
assert(deepEqual(res, clonedRes));
});
it('should call next()', function() {
assert(next.calledOnce);
});
});
...(#other test cases all similar to the first)
describe('When the error is a SyntaxError, with a 400 status, has a `body` property set, and has type `entity.parse.failed`', function() {
err = getValidError();
req = {};
let res = {
status: spy(),
set: spy(),
json: spy()
};
let next = spy();
errorHandler(err, req, res, next);
it('should set res with a 400 status code', function() {
assert(res.status.calledOnce);
assert(res.status.calledWithExactly(400));
});
it('should set res with an application/json content-type header', function() {
assert(res.set.calledOnce);
assert(res.set.calledWithExactly('Content-Type', 'application/json'));
});
it('should set res.json with error code', function() {
assert(res.json.calledOnce);
assert(res.json.calledWithExactly({ message: 'Payload should be in JSON format' }));
});
});
});
Notice that I have let in front of res, next and clonedRes in the describe block for 'When the error is a SyntaxError...'.
Without the let in front of these, I get failures in my tests. I do not understand why I need to add the let for these again, but not for the err and req in that same block. Could anyone help me out with an explanation?
In strict mode (and in decently linted code in general), a variable must be declared before it's assigned to. Also, const and let variables must be declared once in a block, and no more. Re-declaring the err (or any other variable) which has already been declared will throw an error, which is why you should see let <varname> only once in your describe('errorHandler' function:
const describe = cb => cb();
let something;
describe(() => {
something = 'foo';
});
let something;
describe(() => {
something = 'bar';
});
Further describes inside of describe('errorHandler' already have scoped access to err.
Without declaring a variable first at all, assigning to it in sloppy mode will result in it being assigned to the global object, which is almost always undesirable and can introduce bugs and errors. For example:
// Accidentally implicitly referencing window.status, which can only be a string:
status = false;
if (status) {
console.log('status is actually truthy!');
}
That said, it's often a good idea to keep variables scoped as narrowly as possible - only assign to an outside variable when you need the value in the outer scope. Consider declaring the variables only inside of the describes that assign to them, which has an additional bonus of allowing you to use const instead of let:
describe('When the error is not an instance of SyntaxError', function() {
const err = getValidError(Error);
const req = {};
const res = {};
const next = spy();
const clonedRes = deepClone(res);
errorHandler(err, req, res, next);
// etc
});
// etc
describe('When the error is a SyntaxError, with a 400 status, has a `body` property set, and has type `entity.parse.failed`', function() {
const err = getValidError();
const req = {};
const res = {
status: spy(),
set: spy(),
json: spy()
};
const next = spy();
// etc

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"}]');
});

Get data using await async without try catch

I am trying to use await-async without try-catch for this:
const getUsers = async (reject, time) => (
new Promise((resolve, reject) => {
setTimeout(() => {
if (reject) {
reject(....)
}
resolve(.....);
}, time);
})
);
module.exports = {
getUsers ,
};
With try-catch block it looks like this:
const { getUsers } = require('./users');
const users = async () => {
try {
const value = await getUsers(1000, false);
.....
} catch (error) {
.....
}
}
users();
How can I write the same code without using the try-catch block?
Using the promise functions then-catch to make the process simpler I use this utils :
// utils.js
const utils = promise => (
promise
.then(data => ({ data, error: null }))
.catch(error => ({ error, data: null }))
);
module.exports = utils;
And then
const { getUsers } = require('./api');
const utils = require('./utils');
const users = async () => {
const { error, data } = await utils(getUsers(2000, false));
if (!error) {
console.info(data);
return;
}
console.error(error);
}
users();
Without using the try-catch block I got the same output, this way makes it better to understand the code.
In Extension to L Y E S - C H I O U K H's Answer:
The Utils Function is actually correct but, make sure to add the return keyword before the promise as shown down below:
// utils.js
const utils = promise => (
return promise
.then(data => { [data, null]; })
.catch(error => { [null, error]; });
);
module.exports = utils;
When Calling in Main Code:
let resonse, error; // any variable name is fine make sure there is one for error and the response
[response, error] = await utils(any_function()); // Make sure that inside the tuple, response is first and error is last like: [response, error].
if (error) console.log(error);
// -- Do Whatever with the Response -- //
Using My Method Would Give you Benefits like:
Your Own Variable Names.
Not Running into Type Safety issues when using Typescript.
Good Reason to Strong Type your code.
Personally, I have been using this in my code lately, and has reduced some many headaches, my code is cleaner, I don't have to stick with the same variable names, especially when working on a large codebase.
Happy Coding :)
See Ya!
If you have a valid default for the error case you can use the catch method on the getUsers promise and then await a promise whose error will be handled
const users = async () => {
const value = await getUsers(1000, false).catch(e => null);
}
While this approach should work it should be noted that this may mask the case when getUsers returns null vs when it raises an error, and you will still need to check for the null or get a null access error. All in all I would stick with the try { .. } catch (e) { ... } for most casses
A package I found called await-to-js can also help it.
import to from 'await-to-js';
const [err, users] = await to(getUsers());
if(err) doSomething();
The idea is like Lyes CHIOUKH's method, just a wrapper. Copied the source code here.
/**
* #param { Promise } promise
* #param { Object= } errorExt - Additional Information you can pass to the err object
* #return { Promise }
*/
export function to<T, U = Error> (
promise: Promise<T>,
errorExt?: object
): Promise<[U | null, T | undefined]> {
return promise
.then<[null, T]>((data: T) => [null, data])
.catch<[U, undefined]>((err: U) => {
if (errorExt) {
Object.assign(err, errorExt);
}
return [err, undefined];
});
}
export default to;
If you have such above single line async/await function, then this would have been clean code for you:
const getUsers = async (time, shouldReject=false) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(Error('Rejected...'));
} else {
resolve(["User1", "User2"]);
}
}, time);
});
}
const userOperation = users => {
console.log("Operating user", users);
}
// Example 1, pass
getUsers(100)
.then(users => userOperation(users))
.catch(e => console.log(e.message));
// Example 2, rejected
getUsers(100, true)
.then(users => userOperation(users))
.catch(e => console.log(e.message));
And for multiple await in a single async function, it would good to have try/catch block as below:
const users = async () => {
try {
const value = await getUsers(1000, false);
const value1 = await getUsers2(1000, false);
...
} catch (error) {
...
}
}

How can I pass variable into an evaluate function?

I'm trying to pass a variable into a page.evaluate() function in Puppeteer, but when I use the following very simplified example, the variable evalVar is undefined.
I can't find any examples to build on, so I need help passing that variable into the page.evaluate() function so I can use it inside.
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
const evalVar = 'WHUT??';
try {
await page.goto('https://www.google.com.au');
await page.waitForSelector('#fbar');
const links = await page.evaluate((evalVar) => {
console.log('evalVar:', evalVar); // appears undefined
const urls = [];
hrefs = document.querySelectorAll('#fbar #fsl a');
hrefs.forEach(function(el) {
urls.push(el.href);
});
return urls;
})
console.log('links:', links);
} catch (err) {
console.log('ERR:', err.message);
} finally {
// browser.close();
}
})();
You have to pass the variable as an argument to the pageFunction like this:
const links = await page.evaluate((evalVar) => {
console.log(evalVar); // 2. should be defined now
…
}, evalVar); // 1. pass variable as an argument
You can pass in multiple variables by passing more arguments to page.evaluate():
await page.evaluate((a, b c) => { console.log(a, b, c) }, a, b, c)
The arguments must either be serializable as JSON or JSHandles of in-browser objects: https://pptr.dev/#?show=api-pageevaluatepagefunction-args
I encourage you to stick on this style, because it's more convenient and readable.
let name = 'jack';
let age = 33;
let location = 'Berlin/Germany';
await page.evaluate(({name, age, location}) => {
console.log(name);
console.log(age);
console.log(location);
},{name, age, location});
Single Variable:
You can pass one variable to page.evaluate() using the following syntax:
await page.evaluate(example => { /* ... */ }, example);
Note: You do not need to enclose the variable in (), unless you are going to be passing multiple variables.
Multiple Variables:
You can pass multiple variables to page.evaluate() using the following syntax:
await page.evaluate((example_1, example_2) => { /* ... */ }, example_1, example_2);
Note: Enclosing your variables within {} is not necessary.
It took me quite a while to figure out that console.log() in evaluate() can't show in node console.
Ref: https://github.com/GoogleChrome/puppeteer/issues/1944
everything that is run inside the page.evaluate function is done in the context of the browser page. The script is running in the browser not in node.js so if you log it will show in the browsers console which if you are running headless you will not see. You also can't set a node breakpoint inside the function.
Hope this can help.
For pass a function, there are two ways you can do it.
// 1. Defined in evaluationContext
await page.evaluate(() => {
window.yourFunc = function() {...};
});
const links = await page.evaluate(() => {
const func = window.yourFunc;
func();
});
// 2. Transform function to serializable(string). (Function can not be serialized)
const yourFunc = function() {...};
const obj = {
func: yourFunc.toString()
};
const otherObj = {
foo: 'bar'
};
const links = await page.evaluate((obj, aObj) => {
const funStr = obj.func;
const func = new Function(`return ${funStr}.apply(null, arguments)`)
func();
const foo = aObj.foo; // bar, for object
window.foo = foo;
debugger;
}, obj, otherObj);
You can add devtools: true to the launch options for test
I have a typescript example that could help someone new in typescript.
const hyperlinks: string [] = await page.evaluate((url: string, regex: RegExp, querySelect: string) => {
.........
}, url, regex, querySelect);
Slightly different version from #wolf answer above. Make code much more reusable between different context.
// util functions
export const pipe = (...fns) => initialVal => fns.reduce((acc, fn) => fn(acc), initialVal)
export const pluck = key => obj => obj[key] || null
export const map = fn => item => fn(item)
// these variables will be cast to string, look below at fn.toString()
const updatedAt = await page.evaluate(
([selector, util]) => {
let { pipe, map, pluck } = util
pipe = new Function(`return ${pipe}`)()
map = new Function(`return ${map}`)()
pluck = new Function(`return ${pluck}`)()
return pipe(
s => document.querySelector(s),
pluck('textContent'),
map(text => text.trim()),
map(date => Date.parse(date)),
map(timeStamp => Promise.resolve(timeStamp))
)(selector)
},
[
'#table-announcements tbody td:nth-child(2) .d-none',
{ pipe: pipe.toString(), map: map.toString(), pluck: pluck.toString() },
]
)
Also not that functions inside pipe cant used something like this
// incorrect, which is i don't know why
pipe(document.querySelector)
// should be
pipe(s => document.querySelector(s))

Categories

Resources