Here is the TypeScript playground used for this example.
Let’s set up a (rather contrived) problem to motivate the use of the new using
keyword introduced in TypeScript 5.2:
const myFunction = () => { console.group("myFunction")
const myNum = Math.random() * 100 if (myNum < 20) { console.log("didn't work ") return false }
console.log("works!") return true}
myFunction()
We have a function here that fails sometimes. We want to debug it. We can use console.group
to group the logs for this function together. But we need to remember to close the group at the end of the function. If we forget, the groups will continue ad-infinitum, every time the function is called.
Of course, it’s not a big deal to just call console.groupEnd
before every return:
const myFunction = () => { console.group("myFunction")
const myNum = Math.random() * 100 if (myNum < 20) { console.log("didn't work ") console.groupEnd() return false }
console.log("works!") console.groupEnd() return true}
myFunction()
But suppose it is a hairy function with many returns. It’s easy to forget to close the group. We can use the using
keyword to ensure that the group is closed when the function returns.
To start, we need to polyfill Symbol.dispose
, which is still in TC39 stage 3.
// <polyfill>
// the interfaces are only necessary if you're not including https://github.com/microsoft/TypeScript/blob/main/src/lib/esnext.disposable.d.ts as a `lib` optioninterface SymbolConstructor { readonly dispose: unique symbol}
interface Disposable { [Symbol.dispose](): void}
// @ts-ignore - if it already exists as a readonly property, this is a no-op anywaySymbol.dispose ??= Symbol('Symbol.dispose')
// </polyfill>
const myFunction = () => { console.group("myFunction")
const myNum = Math.random() * 100 if (myNum < 20) { console.log("didn't work ") console.groupEnd() return false }
console.log("works!") console.groupEnd() return true}
myFunction()
Then, we need to define a disposable resource:
15 collapsed lines
// <polyfill>
// the interfaces are only necessary if you're not including https://github.com/microsoft/TypeScript/blob/main/src/lib/esnext.disposable.d.ts as a `lib` optioninterface SymbolConstructor { readonly dispose: unique symbol}
interface Disposable { [Symbol.dispose](): void}
// @ts-ignore - if it already exists as a readonly property, this is a no-op anywaySymbol.dispose ??= Symbol('Symbol.dispose')
// </polyfill>
const logGroup = (groupName: string) => { console.group(groupName) return { [Symbol.dispose]: () => { console.groupEnd() } }}
const myFunction = () => { const myNum = Math.random() * 100 if (myNum < 20) { console.log("didn't work ") return false }
console.log("works!") return true}
myFunction()
When the return value of logGroup
leaves scope, it will be disposed. We can use the using
keyword for this:
15 collapsed lines
// <polyfill>
// the interfaces are only necessary if you're not including https://github.com/microsoft/TypeScript/blob/main/src/lib/esnext.disposable.d.ts as a `lib` optioninterface SymbolConstructor { readonly dispose: unique symbol}
interface Disposable { [Symbol.dispose](): void}
// @ts-ignore - if it already exists as a readonly property, this is a no-op anywaySymbol.dispose ??= Symbol('Symbol.dispose')
// </polyfill>
const logGroup = (groupName: string) => { console.group(groupName) return { [Symbol.dispose]: () => { console.groupEnd() } }}
const myFunction = () => { using _ = logGroup("myFunction")
const myNum = Math.random() * 100 if (myNum < 20) { console.log("didn't work ") return false }
console.log("works!") return true}
myFunction()
Now our groups are always closed:
Learn more about what you can do with using
here: https://www.totaltypescript.com/typescript-5-2-new-keyword-using.