65

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?

nem035
  • 34,790
  • 6
  • 87
  • 99
Justin
  • 42,716
  • 77
  • 201
  • 296
  • 2
    What do you expect to happen with `console.log(configPath);` if an error is thrown? – Felix Kling Dec 02 '16 at 05:11
  • Not the only way. `var` is still exist. so use `var` inside the `try{}catch(){}` – pery mimon Dec 19 '18 at 13:36
  • @FelixKling In that situation I expect `configPath === undefined`. – Dai Oct 27 '20 at 19:48
  • It would be great if there was an inline try-catch expression or if try block could return a value to the outer scope. `const value = try path.resolve() : undefined` or `const value = try { ... } catch(e) { .. }`. – Qwerty Feb 17 '21 at 19:43

8 Answers8

103

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);
nem035
  • 34,790
  • 6
  • 87
  • 99
  • 5
    Is using var in this instance frowned upon, and should we always try to use let and const now? Or is var still ok for these kinds of use cases? – hamncheez Feb 28 '19 at 22:47
  • 7
    @hamncheez the main issue with using `var` in this manner is that it introduces an additional cognitive overhead to the programmer. Such a situation makes it difficult to easily tell all the portions of the scope that this `var` is leaking into since it is actually global to the function (even though it is defined within a smaller block). This is particularly an issue in bigger functions or perhaps functions where similar (or same) variable names are used; `var` inherently requires more thinking to comprehend its behavior and is thus more prone to lead to bugs. – nem035 Feb 28 '19 at 22:52
  • 2
    I like the last method using `var`. However I'd always put `configPath = undefined;` in the catch block. Especially when the block is inside a loop, to avoid it to have the value from a previous iteration. – barney765 Jan 26 '20 at 13:57
  • Both the `let` & `var` case will result in a typescript error: `Variable 'configPath' is used before being assigned. ts(2454)` – James Gentes Jan 09 '23 at 19:34
31
'use strict';

const path = require('path');

const configPath = (function() {
  try {
    return path.resolve(process.cwd(), config);
  } catch (error) {
    //.....
  }
})()

console.log(configPath);
steph643
  • 2,343
  • 1
  • 23
  • 20
4

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);
yunzen
  • 32,854
  • 11
  • 73
  • 106
  • I am not sure if using this or a function to assign the value is the way to go. This can be risky if you work with objects and forget to reset the temp. For instance: let temp = {a: 2}; const notemp = temp; temp.b = 3; will make booth temp and notemp into {a: 2, b:3} – Håkan KA Jun 27 '18 at 09:05
  • Why not use `delete temp`? – Zer0 Apr 10 '20 at 13:20
  • 1
    @Zer0 because in JavaScript the [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete) operator "has nothing to do with directly freeing memory". It "removes a given property from an object". And `temp` is initialized to a string primitive that isn't a [property](https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript) of an object. And [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), "unlike `var`, does not create a property on the global object". So `temp` doesn't refer to a property of an object. – ma11hew28 Jan 23 '21 at 20:09
3

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
      },
    })
  })
})

Eric Wooley
  • 646
  • 4
  • 17
3

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

Pierre Spring
  • 10,525
  • 13
  • 49
  • 44
Alteredorange
  • 556
  • 1
  • 6
  • 23
  • Bear in mind that whatever is returned from the .catch will be assigned to the result. In both these examples the result will get a value of undefined (as discussed in the comments of the answer you linked). – sorohan Mar 08 '22 at 23:55
1

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.

Zer0
  • 1,580
  • 10
  • 28
1

You can just do:


  const result = await promise.catch(err=>{
    console.log(err)
  })

-3

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!
Kyle Lin
  • 827
  • 8
  • 16