A special method decorator in Typescript that ensures a decorated method is only executed one time

I have a task to create a method decorator that ensures a decorated method is only executed once.
Here's an example:

   class Example {
        data: any;
        @executeOnce
        setData(newData: any) {
            this.newData = newData;
        }
    }
    const example = new Example();
    example.setData([1,2,3]);
    console.log(example.data); // [1,2,3]
    example.setData('new string');
    console.log(example.data); // [1,2,3]  

I've been trying different approaches to prevent a function from being called twice without success. My unit tests are failing as well. Here's what I have so far:

const executeOnce = (
    target: Object,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
) => {
    const method = descriptor.value;
    
    descriptor.value = function (...args){
        // ???
    }
};  

Unit tests:

describe('executeOnce', () => {
    it('should execute method once with single argument', () => {
        class Test {
            data: string;
            @executeOnce
            setData(newData: string) {
                this.data = newData;
            }
        }
        const test = new Test();
        test.setData('first string');
        test.setData('second string');
        assert.strictEqual(test.data, 'first string')
    });

    it('should execute method once with multiple arguments', () => {
        class Test {
            user: {name: string, age: number};
            @executeOnce
            setUser(name: string, age: number) {
                this.user = {name, age};
            }
        }
        const test = new Test();
        test.setUser('John',22);
        test.setUser('Bill',34);
        assert.deepStrictEqual(test.user, {name: 'John', age: 22})
    });

    it('should always return the result of the first execution', () => {
        class Test {
            @executeOnce
            sayHello(name: string) {
                return `Hello ${name}!`;
            }
        }
        const test = new Test();
        test.sayHello('John');
        test.sayHello('Mark');
        assert.strictEqual(test.sayHello('new name'), 'Hello John!')
    })
});  

Can you offer me some assistance? Thank you in advance!

Answer №1

Consider implementing a function like the one below:

const executeOnce = (
  target: Object,
  propertyKey: string | symbol,
  descriptor: PropertyDescriptor
) => {
  const method = descriptor.value;
  let hasExecuted = false;

  descriptor.value = function (...args: any[]) {
    if (hasExecuted) { return; }
    hasExecuted = true;
    method(...args);
  }
};

Answer №2

Using reflect-metadata can be quite useful in this situation. You might want to consider implementing something similar to the following:

import 'reflect-metadata';

const metadataKey = Symbol('initialized');

export function executeOnce(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const method = descriptor.value;
    descriptor.value = function(...args) {
        const initialized = Reflect.getMetadata(metadataKey, target, propertyKey);

        if (initialized) {
            return;
        }

        Reflect.defineMetadata(metadataKey, true, target, propertyKey);

        method.apply(this, args);
    }
}

For more details, you can refer to the following resource: https://www.typescriptlang.org/docs/handbook/decorators.html#metadata

Answer №3

My approach to a problem similar to this involved creating a function called 'once' with the following implementation:

function once(_target: any, _methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor {
    const originalMethod = descriptor.value;
    let isFirstTime = true;
    descriptor.value = function (...args: any[]) {
        if (isFirstTime) {
            isFirstTime = false;
            this.previousValue = originalMethod.apply(this, args);
        } else {
            return this.previousValue;
        }
    };
    return descriptor;
}

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

What is the process for activating a disabled checkboxradio in JQuery UI?

My challenge is with a fancy looking checkbox that needs to be toggled between enabled and disabled based on certain actions on the page. The checkbox has been customized using jquery-ui for a sleek appearance. Below is an example of the code snippet: &l ...

Adjusting the size of an HTML5 canvas using a class function when a resize event occurs

I'm attempting to adjust the size of a canvas element using a resizeCanvas() method triggered by a resize event. When I directly call the method, the canvas resizes without any issues. However, when the event handler triggers subsequent calls, I encou ...

Implementing a click event on a selection option – tips and tricks

When I want to select an option, I can use the following script: Javascript $("#practice-area optgroup option").click(function(){ // insert your function here }); HTML <select name="practice-area" id="practice-area"> <option value="0">S ...

angular 4 observable is yielding an [object Object]

My frontend is built using Angular 4, while my backend consists of Laravel 5.5 serving as the restful API. When interacting with the backend, everything works smoothly as I am able to send curl requests and receive back the expected JSON response with 2 ke ...

Efficiently finding a group of substrings within a JavaScript string

I am currently working on a method to efficiently search for specific substrings within a given string. Here is my current implementation: const apple = "apple" const banana = "banana" const chickoo = "chickoo" const dates = & ...

JavaScript nested function scope loss issue is being faced

Could someone provide an explanation of the scope binding in this code snippet? window.name = "window"; object = { name: "object", method: function() { nestedMethod: function() { console.log(this.name); ...

What is causing this to function properly on Firefox but not on Chrome or IE?

After clicking the process_2banner button on my html page, a piece of code is executed. Surprisingly, this code performs as expected in Firefox, but not in Chrome or Internet Explorer. In those browsers, although the ajax code is triggered, the div spinner ...

The HTML table fails to refresh after an Ajax request has been made

I am currently utilizing Ajax to delete a row from the Roles table. The functionality allows the user to add and delete roles using a link for each row. While the deletion process works properly and removes the row from the database, the table does not get ...

Try switching out the set timeout function within typescript for Wavesurfer.js

I recently wrote a function that changes the source for an audio file using wavesurfer.js and then plays the song with the newly loaded audio file. While my code is currently functioning as intended, I can't help but feel there might be a more efficie ...

In Express.js, what is the best way to redirect to a specific route after sending a response to the client side?

As a beginner learning Express.JS, I am facing an issue with redirecting routes. My goal is to display a message to the user saying "Your data has been registered successfully." and then redirect them back to the form page after a certain time period. Howe ...

AWS: The identity pool configuration is not valid. Verify the IAM roles assigned to this pool for any errors

I have successfully set up a user pool and an identity pool. Utilizing the JavaScript SDK, I can successfully signup, send confirmation codes, and confirm users. However, I encounter an error when trying to authenticate a user and retrieve credentials us ...

Tips for Programmatically Choosing a Single Value Option in Angular

I have implemented a "Select All" feature in my select list to clear all other selected options and only choose the "All" option. However, I am facing an issue where I can clear out all selected options but cannot select the "All" option. <mat-form-fiel ...

Adjusting the background color of a MuiList within a React Material-UI Select component

Looking to customize the background color of the MuiList in a react material-ui Select component without affecting other elements. Specifically targeting the Select element with the name 'first'. Despite setting className and trying different cl ...

Creating a mesh parallel to the xy-plane in Three.js

Looking to brush up on my CG fundamentals. How can I create a mesh (such as a circle) that is perfectly parallel to the xy-plane in the direction the camera is facing? For instance, I want it to be normal to the direction the camera is facing and not tilt ...

Guide to building an interface for an object containing a nested array

While working on my Angular/TypeScript project, I encountered a challenge in processing a GET request to retrieve objects from an integration account. Here is a snippet of the response data: { "value": [ { "properties": { ...

Preventing navigation to other tabs in Bootstrap tabs during validation

I am running validation on tab one input fields when the second tab is clicked. Please take a look at my HTML code: <ul class="nav nav-tabs makeblock" role="tablist"> ...

To trigger a Bootstrap 5 modal in a child component from a button click in the parent component in Angular without the need to install ng-bootstrap is possible with the following approach

To achieve the functionality of opening a modal in a child component upon clicking a button in the parent component without using ngx-bootstrap due to restrictions, one approach is to add data-bs-target and data-bs-toggle attributes to the button. Addition ...

Having trouble accessing the value of an item in a dropdown box on a website

I am currently developing a webpage using HTML5 and Knockout Js. I am facing an issue where I am unable to retrieve the ID of the selected item from a dropdown box in order to insert a value into an SQL table. As an example, consider the following SQL tab ...

A guide to accurately fetching the transform properties of an SVG element within a d3.transition

Currently, I am experimenting with d3 animations using d3.transitions specifically involving circles. Consider the circle animation example below (d3.transition()): animationTime = 500; svg = d3.select('#svg'); // Locate th ...

Ways to guarantee that a function runs prior to a console.log in Mongoose

I've created a function called findUser that I'm working on implementing using sails, MongoDb, and mongoose. Here's what the function looks like: findUser(userId); function findUser(user_id){ User.findOne({ _id: user_id ...