A Defer statement in TypeScript that mimics Go's functionality

Is there an equivalent to Go's Defer statement in TypeScript?

I find it tedious to write cleanup code in various parts of a function. Searching for a simpler alternative.

I tried searching on Google, but couldn't locate any relevant information.

Answer №1

One could argue that the answer is no, however, there are a few alternatives available:

  1. As mentioned by @bereal, you can utilize a try/finally for this purpose. In regards to your comment about avoiding the use of try-catch blocks due to them being expensive:

    Yes, throwing an Error can be costly (especially in terms of stack info creation), but entering a try block itself does not incur much overhead. Additionally, JavaScript allows for throwing items other than Error instances, although it's generally recommended to stick with Error. The finally blocks do have minimal overhead, though recent tests in modern engines showed this to be negligible.

  2. An alternative approach would be to assign a function to a variable and execute it at the conclusion of the function execution. While this method may be more costly than using try/finally for a single cleanup task, it could prove advantageous when handling multiple cleanups that might require nested try/finally blocks.

For illustration purposes, here are some sample scenarios:

Single cleanup using try/finally:

function example() {
    try {
        console.log("hello");
    } finally {
        console.log("world");
    }
}
example();

Multiple cleanups using try/finally:

function example() {
    try {
        console.log("my");
        try {
            console.log("dog");
        } finally {
            console.log("has");
        }
    } finally {
        console.log("fleas");
    }
}
example();

Single cleanup via assigned function:

function example() {
    let fn = null;
    fn = () => console.log("world");
    console.log("hello");
    if (fn) { // Conditional check for the above function assignment
        fn();
    }
}
example();

Multiple cleanups using 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();

Alternatively:

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();

Answer №2

If you are looking for a more concise method to implement a defer functionality in TypeScript similar to Go's behavior, consider the following approach:

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 provided code snippet, a timer is set to output 'interval' every second, and a defer function is defined to clear the interval when exiting the function scope (similar to Go programming language).

Upon execution, the

await new Promise(resolve => setTimeout(resolve, 1500)
will introduce a delay of 1.5 seconds, resulting in one 'interval' output before program termination.

$ ts-node src/defer.ts 
interval
$

The comprehensive code example showcased below demonstrates the implementation of this concept in TypeScript version 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 )

This proof-of-concept code serves as an illustrative example and is not suitable for production use.

If you have suggestions on improving this approach or alternative methods in TypeScript, please feel free to share your insights for discussion.

Answer №3

Perhaps this insight will be of assistance, courtesy of the original response from T.J. Crowder (@t-j-crowder).

Implement deferral by utilizing variables in the finally block of a try/catch statement

function example() {
    const defers = [];
    try {
        var xx = "First";
        defers.push(((text) => () => console.log(text))(xx));
        xx = "Not First";
        var yy = "Last";
        defers.push(((text) => () => console.log(text))(yy));
        yy = "Not Last"
        const timer = setInterval(() => console.log('live'), 1000);
        defers.push(((t) => () => clearInterval(t))(timer));
    } finally {
        while (defers.length) {
            const fn = defers.pop();
            fn();
        }
    }
}
example();

Answer №4

This is the most suitable approach that we can take. Insert a call back in the middle of the function and then invoke that function at the end.


async function example() {
   let completeFunction = () => {
      console.log("The process has finished")
    }
   console.log("Starting the process");
   await delay(2000);
   console.log("Process completed");
   completeFunction();
}
example()

function delay(ms) {
  return new Promise(resolve=> setTimeout(resolve, ms))
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to retrieve the type of a generic class in JavaScript?

class Alpha { static construct<T extends typeof Alpha>(this: T): InstanceType<T> { const v = new Alpha(); return v as InstanceType<T>; } } class Beta extends Alpha {} const x = Alpha.construct(); // generates Alpha const y = ...

Differentiating AWS API errors in TypeScript: A guide

How can I write different handlers in TypeScript for ThrottlingException and ExecutionLimitExceeded when starting a StepFunction execution? new StepFunction.startExecution({}, (err, data) => { if (err) { // Need to identify ThrottlingExcepti ...

Can you explain the distinction between declaring type using the colon versus the as syntax?

Can you explain the variation between using the : syntax for declaring type let serverMessage: UServerMessage = message; and the as syntax? let serverMessage = message as UServerMessage; It appears that they yield identical outcomes in this particular ...

What is a way to construct an object without resorting to casts or manually declaring variables for each field?

Click here for a hands-on example. Take a look at the code snippet below: export type BigType = { foo: number; bar?: number; baz: number; qux?: string[]; }; function BuildBigType(params: string[]) { // Here's what I'd like to do: ...

Having trouble typing computed values in Vue Composition API with TypeScript?

I'm attempting to obtain a computed value using the composition API in vue, and here is the code I am working with: setup() { const store = useStore(); const spaUrls = inject<SpaUrls>('spaUrls'); const azureAd = inject<AzureAd ...

TS7030: In Angular13, ensure that all code paths within the guard and canActivate methods return a value

Having trouble using guards for an unlogged user and constantly facing errors. Error: TS7030 - Not all code paths return a value. Below is my auth.guard.ts file: import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from &a ...

What is the process of creating a new array by grouping data from an existing array based on their respective IDs?

Here is the initial array object that I have: const data = [ { "order_id":"ORDCUTHIUJ", "branch_code":"MVPA", "total_amt":199500, "product_details":[ { ...

Creating an array in TypeScript is a versatile and powerful feature that

While I have some familiarity with TypeScript, there is one thing that continues to intrigue me. I understand the distinction between Array<string> and string[]. I am aware that these declarations can be used interchangeably, such as: export class S ...

Can the garbage collector in Typescript/JavaScript effectively handle circular references, or does it result in memory leaks?

When working in languages such as C#, managing memory is not a problem. However, this can lead to difficult-to-find memory bugs in other languages. Is it safe to use the following code snippet in Typescript or Javascript without encountering any issues? c ...

Utilize Function type while preserving generics

Is there a way in Typescript to reference a function type with generics without instantiating them, but rather passing them to be instantiated when the function is called? For instance, consider the following type: type FetchPageData<T> = (client : ...

What is the best way to send multiple values from node.js to typescript?

My Node.js post API currently returns a token, but I want it to include the user's email, id, etc: app.post('/auth', function (req, response) { const body = req.body; console.log(req.body); let query = `select * from users wher ...

Is time-based revalidation in NextJS factored into Vercel's build execution time?

Currently overseeing the staging environment of a substantial project comprising over 50 dynamic pages. These pages undergo time-based revalidation every 5 minutes on Vercel's complimentary tier. In addition, I am tasked with importing data for numer ...

Displaying nested objects within an object using React

Behold this interesting item: const [object, setObject] = useState ({ item1: "Greetings, World!", item2: "Salutations!", }); I aim to retrieve all the children from it. I have a snippet of code here, but for some reason, i ...

Learn how to efficiently transfer row data or an array object to a component within Angular by utilizing the MatDialog feature

My goal is to create a functionality where clicking on a button within a specific row opens up a matDialog box displaying all the contents of that row. Below is the HTML snippet: <tr *ngFor="let u of users"> <td data-label="ID& ...

Is it possible in Typescript to assign a type to a variable using a constant declaration?

My desired outcome (This does not conform to TS rules) : const type1 = "number"; let myVariable1 : typeof<type1> = 12; let type2 = "string" as const; let myVariable2 : typeof<type2> = "foo"; Is it possible to impl ...

Creating and utilizing multi-module NPM packages written in Typescript: A comprehensive guide

For a while now, I've been quite at ease creating and utilizing NPM packages with Typescript. However, these packages have typically been provided and consumed as a single module. Now, I'm interested in publishing packages that contain more than ...

Sorting elements in an array based on an 'in' condition in TypeScript

I am currently working with an arrayList that contains employee information such as employeename, grade, and designation. In my view, I have a multiselect component that returns an array of grades like [1,2,3] once we select grade1, grade2, grade3 from the ...

What's the best way to maintain the return type of a function as Promise<MyObject[]> when using forEach method?

I am currently working with a function called search, which at the moment is set up to return a type of Promise<MyObject[]>: export function search(args: SearchInput) { return SomeInterface.performSearch(args) .then(xmlRequest =&g ...

A guide on accessing a dynamic object key in array.map()

How can I dynamically return an object key in array.map()? Currently, I am retrieving the maximum value from an array using a specific object key with the following code: Math.max.apply(Math, class.map(function (o) { return o.Students; })); In this code ...

Referring to TypeScript modules

Consider this TypeScript code snippet: module animals { export class Animal { } } module display.animals { export class DisplayAnimal extends animals.Animal { } } The objective here is to create a subclass called DisplayAnimal in the dis ...