Does TypeScript have anything similar to Go's Defer statement?
I am tired of writing cleanup code in multiple locations throughout a function. Looking for an easier solution.
I have done a quick Google, but didn't find anything.
Does TypeScript have anything similar to Go's Defer statement?
I am tired of writing cleanup code in multiple locations throughout a function. Looking for an easier solution.
I have done a quick Google, but didn't find anything.
Arguably the answer is no, but you have at least a couple of options:
As @bereal mentioned, you'd use a try/finally for that. Re try/finally, in a comment you said:
Yep, except I don't want to use a try catch block as they can be expensive.
Not really. Throwing an exception is expensive; entering a try block is not. finally blocks have slight overhead, but it happens I had to measure this in a couple of modern engines recently and it was truly amazingly trivial.
You can assign a function to a variable and then run it at the end of the function (or perhaps use an array if you want to do several). For the single cleanup, I'd expect it to be more expensive than try/finally. For multiple (which would otherwise require nested try/finally blocks), well, you'd have to find out.
FWIW, some examples:
A single cleanup in a try/finally:
function example() {
try {
console.log("hello");
} finally {
console.log("world");
}
}
example();
Multiple cleanup in a try/finally:
function example() {
try {
console.log("my");
try {
console.log("dog");
} finally {
console.log("has");
}
} finally {
console.log("fleas");
}
}
example();
Single cleanup via assigning a function:
function example() {
let fn = null;
fn = () => console.log("world");
console.log("hello");
if (fn) { // Allowing for the above to be conditional, even though
// it isn't above
fn();
}
}
example();
Multiple cleanup in a try/finally:
function example() {
const cleanup = [];
cleanup.push(() => console.log("has"));
console.log("my");
cleanup.push(() => console.log("fleas"));
console.log("dog");
cleanup.forEach(fn => fn());
}
example();
Or in the other order:
function example() {
const cleanup = [];
cleanup.push(() => console.log("fleas"));
console.log("my");
cleanup.push(() => console.log("has"));
console.log("dog");
while (cleanup.length) {
const fn = cleanup.pop();
fn();
}
}
example();
What I want is a clearer way to do a defer job as same as Go in TypeScript:
class Test {
@Defer()
async test () {
const timer = setInterval(() => console.log('interval'), 1000)
defer.call(this, () => clearInterval(timer))
await new Promise(resolve => setTimeout(resolve, 1500))
}
}
const t = new Test()
t.test()
.catch(console.error)
In the above code, we define a timer to output interval for every 1 second, and define a defer to clear interval when out of this function scope (the same as Go).
When running it, the await new Promise(resolve => setTimeout(resolve, 1500) will wait 1.5 seconds, which will output one line of interval output, then the program exit.
$ ts-node src/defer.ts
interval
$
The following code example is the full code which can be run directly by copy/paste with TypeScript 4.4:
const DEFER_SYMBOL = Symbol('defer')
type Callback = (err?: Error) => void
interface DeferizedThis {
[DEFER_SYMBOL]?: Callback[],
}
function Defer () {
return function callMethodDecorator (
_target : any,
_propertyKey : string,
descriptor : PropertyDescriptor,
): PropertyDescriptor {
const oldValue = descriptor.value
async function deferizedMethod (
this: DeferizedThis,
...args: any[]
) {
try {
const ret = await oldValue.apply(this, args)
return ret
} finally {
if (this[DEFER_SYMBOL]) {
const deferCallbacks = this[DEFER_SYMBOL]
while (true) {
const fn = deferCallbacks?.pop()
if (!fn) { break }
try { fn() } catch (e) { console.error(e) }
}
}
}
}
descriptor.value = deferizedMethod
return descriptor
}
}
function defer (
this: any,
cb: Callback,
): void {
if (this[DEFER_SYMBOL]) {
this[DEFER_SYMBOL]!.push(cb)
} else {
this[DEFER_SYMBOL] = [cb]
}
}
class Test {
@Defer()
async test () {
const timer = setInterval(() => console.log('interval'), 1000)
defer.call(this, () => clearInterval(timer))
await new Promise(resolve => setTimeout(resolve, 1500))
}
}
const t = new Test()
t.test()
.catch(console.error)
If you have read the above code, it's clearly a buggy PoC and definitely can not be used in production.
I'd like to discuss if we have any good ways to achieve this in TypeScript, by following this decorator way, or any direction else.
Maybe this will help, From original T.J. Crowder(@t-j-crowder) Response.
Defer by referencing variables in the finally block of a try/catch block
function example() {
const defers = [];
try {
var xx = "Firts";
defers.push(((text) => () => console.log(text))(xx));
xx = "No Firts";
var yy = "Last";
defers.push(((text) => () => console.log(text))(yy));
yy = "No Last"
const timer = setInterval(() => console.log('live'), 1000);
defers.push(((t) => () => clearInterval(t))(timer));
} finally {
while (defers.length) {
const fn = defers.pop();
fn();
}
}
}
example();