Creating a function with a flexible number of parameters assigned to a variable:

When setting up a Socket.IO client, I have multiple methods structured like the following:

myobject.prototype.A = function (callback) {
    this.foo('a', null, callback);
}

myobject.prototype.B = function (bar, callback) {
    this.foo('b', { bar }, callback);
}

myobject.prototype.C = function (baz, qux, callback) {
    this.foo('c', { baz, qux }, callback);
}

The specific details of this.foo are not important, but it requires 3 parameters: a string, an object created from the method's parameters, and a callback function.

I want to centralize the setup for these methods. My goal is to create something similar to this structure:

// Unsure about the format of args
const methods = {
  A: { socketName: 'a', args: [ ] },
  B: { socketName: 'b', args: [ 'bar' ] },
  C: { socketName: 'c', args: [ 'baz', 'qux' ] }
};

for (let m in methods) {
    const mData = methods[m];
    this.prototype[m] = function (what_do_I_put_here_?, callback) {
        // How do I construct "otherArgs"?
        this.foo(mData.socketName, otherArgs, callback);
    }
}

I believe utilizing destructuring assignments might be helpful, although I am uncertain on how to implement them in this particular scenario.

Answer №1

To accomplish this, you can utilize closures along with an array function:

"use strict"
class Test {

  createDynamicFunction(name, propertyNames = []) {
    // return an arrow function (ensures that 'this' refers to the object)
    return (x, ...args) => {
      let obj = null; // set obj to null by default
      if (args.length > 0) {
        // if there are additional arguments to x, we create an object
        obj = {};
        // map the additional arguments to the property names
        propertyNames.forEach((value, idx) => {
          obj[value] = args[idx]
        })
      }
      // call the actual function
      return this.functionName(name, x, obj)
    }
  }

  functionName(name, x, obj) {
    console.log(`${name} ${x}`)
    console.dir(obj);
  }
}

let test = new Test();


let triggerForA = test.createDynamicFunction('a');
let triggerForB = test.createDynamicFunction('b', ['y']);
let triggerForC = test.createDynamicFunction('c', ['y', 'z']);

triggerForA(1);
triggerForB(1, 2);
triggerForC(1, 2, 3);

If you require them as member functions specifically, you could implement it like this:

"use strict"
class Test {

  constructor() {
    this.A = this.createDynamicFunction('a');
    this.B = this.createDynamicFunction('b', ['y']);
    this.C = this.createDynamicFunction('c', ['y', 'z']);
  }


  createDynamicFunction(name, propertyNames = []) {
    // return an arrow function (ensures that 'this' refers to the object)
    return (x, ...args) => {
      let obj = null; // set obj to null by default
      if (args.length > 0) {
        // if there are additional arguments to x, we create an object
        obj = {};
        // map the additional arguments to the property names
        propertyNames.forEach((value, idx) => {
          obj[value] = args[idx]
        })
      }
      // call the actual function
      return this.functionName(name, x, obj)
    }
  }

  functionName(name, x, obj) {
    console.log(`${name} ${x}`)
    console.dir(obj);
  }
}

let test = new Test();


test.A(1);
test.B(1, 2);
test.C(1, 2, 3);

If the focus is solely on member functions, you can eliminate the arrow function through this method:

"use strict"



function createDynamicPrototypeMethod(classObj, targetFunctionName, newFunctionName, name, propertyNames = []) {
  // create a new function and assign it to the prototype of the class
  classObj.prototype[newFunctionName] = function(x, ...args) {
    let obj = null; // set obj to null by default
    if (args.length > 0) {
      // if there are additional arguments to x, we create an object
      obj = {};
      // map the additional arguments to the property names
      propertyNames.forEach((value, idx) => {
        obj[value] = args[idx]
      })
    }
    // call the actual function
    return this[targetFunctionName](name, x, obj)
  }
}

class Test {
  functionName(name, x, obj) {
    console.log(`${name} ${x}`)
    console.dir(obj);
  }
}

createDynamicPrototypeMethod(Test, 'functionName', 'A', 'a');
createDynamicPrototypeMethod(Test, 'functionName', 'B', 'b', ['y']);
createDynamicPrototypeMethod(Test, 'functionName', 'C', 'c', ['y', 'z']);

let test = new Test();

test.A(1);
test.B(1, 2);
test.C(1, 2, 3);

Without knowing the exact use case, it's challenging to provide a solution that perfectly aligns with the requirements. However, based on the code provided, you should have a good grasp on how to achieve this.

Answer №2

const createObject = (keys, values) =>
  keys.length === 0
    ? null
    : _.zipObject(keys, values); 

function decorateFunction(fn, name, keys) {
  return function(...args) {
    const callback = args.pop();
    const obj = createObject(keys, args);
    
    return fn.call(this, name, obj, callback);
  }
}

class Foo {
  foo(name, obj, callback) { 
    console.log(this.bar(name, obj, callback));
  }
  
  bar(name, obj, callback) {
    return `name: '${name}'; obj: '${JSON.stringify(obj)}', callback: '${callback}'`;
  }
}

const methods = {
  A: { socketName: 'a', args: [ ] },
  B: { socketName: 'b', args: [ 'bar' ] },
  C: { socketName: 'c', args: [ 'baz', 'qux' ] },
};

for (let m in methods) {
    const mData = methods[m];
    Foo.prototype[m] = decorateFunction(Foo.prototype.foo, mData.socketName, mData.args);
}

const foo = new Foo();

foo.A(() => "this is A's callback");
foo.B("hello", () => "this is B's callback");
foo.C("hello", "world", () => "this is C's callback");
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="80ecefe4e1f3e8c0b4aeb1b7aeb2b1">[email protected]</a>/lodash.min.js"></script>

This approach uses a function that can wrap or decorate other functions and ensure the correct parameters are passed. Function.call also maintains the value of this when calling another function, preserving its context as expected:

/**
 * Decorate functions to standardize parameter application.
 *
 * @param {Function} fn - function to decorate
 * @param {string} name - first parameter to pass
 * @param {Array<string|number|Symbol>} keys - keys for the object which 
 * will be the second parameter of `fn`
 * 
 * @return {Function} function that accepts variable number 
 * of arguments - the number of `keys` plus one callback. Forwards the 
 * value of `this` to the function.
 */
function decorateFunction(fn, name, keys) {
  return function(...args) {
    const callback = args.pop();
    const obj = createObject(keys, args);
    
    return fn.call(this, name, obj, callback);
  }
}

The function createObject takes keys and values and creates an object from them. It utilizes _.zipObject() from Lodash. This implementation can be customized if necessary.

An example call would look like this:

decorateFunction(someFn, "b", [ 'bar' ]

This produces a function that can be called as follows:

wrappedFn("hello", () => "some callback")

Which internally calls:

someFn("b", [ 'bar' ], () => "some callback")

const createObject = (keys, values) =>
  keys.length === 0
    ? null
    : _.zipObject(keys, values); 

function decorateFunction(fn, name, keys) {
  return function(...args) {
    const callback = args.pop();
    const obj = createObject(keys, args);

    return fn.call(this, name, obj, callback);
  }
}

function someFn(name, obj, callback) {
  console.log(`name: '${name}'; obj: '${JSON.stringify(obj)}', callback: '${callback}'`);
}
const wrappedFn = decorateFunction(someFn, "b", [ 'bar' ]);

wrappedFn("hello", () => "some callback");
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7a16151e1b09123a4e544b4d54484b">[email protected]</a>
/lodash.min.js"></script>

Two things to note:

  • The verification of the number of keys and values is not implemented here. Additional logic must be added if validation is required.
  • The length property of the wrapped function may report zero due to how rest parameters work. Therefore, relying on this property for counting parameters before invocation could lead to unreliable results.

Answer №3

A potential strategy's primary aspect would involve utilizing a solitary, argument-handling, this-context conscious function and binding,

The specific arguments are retrieved using the rest parameter syntax.

The anticipated callback function might be popped from the args array, which alters the array and leaves only the remaining argument values that should be passed as values of an object structured like a payload where the keys of the object are set by the pre-bound paramNames array.

The payload is formed via a mapping operation which generates an array of key-value pairs that is then forwarded to Object.fromEntries.

The actual creation from a configuration of parameters and the assignment of each created method to a designated object can be accomplished by reducing the entries of such a config object.

function forwardWithBoundContextAndParamsConfig(
  socketName, paramNames, ...args
) {
  const callback =
    // ... ... ?? always assure at least a callback.
    args.pop() ?? (_ => _);

  const payload = (paramNames.length === 0)
    ? null
    : Object.fromEntries(
        paramNames.map((key, idx) => [key, args[idx] ])
      );

  // - forwarding upon the correct context
  //   and with the correct parameter data
  //   derieved from the bound configuration.
  return this.foo(socketName, payload, callback);
}

const methodConfig = {
  A: { socketName: 'a', paramNames: [] },
  B: { socketName: 'b', paramNames: ['bar'] },
  C: { socketName: 'c', paramNames: ['baz', 'qux'] },
};

class SocketTest {
  foo(socketName, payload, callback) {
    console.log({ socketName, payload, callback });
  }
}
Object
  .entries(methodConfig)
  .reduce((target, [methodName, { socketName, paramNames }]) => {

    target[methodName] = forwardWithBoundContextAndParamsConfig
      .bind(SocketTest.prototype, socketName, paramNames);

    return target;

  }, SocketTest.prototype);

console.log({
  SocketTest,
  'SocketTest.prototype': SocketTest.prototype,
});
const socketTest = new SocketTest;

socketTest.A();
socketTest.A(function callBack_A() {});

socketTest.B('quick', function callBack_B() {});
socketTest.C('brown', 'fox', function callBack_C() {});
.as-console-wrapper { min-height: 100%!important; top: 0; }

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

Updating an array of input values dynamically in React using Material UI TextField

I need to update the project name in an array of available projects. How can I accomplish this using React without affecting all input TextFields in the array? // Here is a sample array of projects const projects = [ { _id: 1, name: 'Project 1' ...

Utilize Vue JS to trigger a query when an option is selected in a drop-down select

I'm new to Vue and facing a challenge. I have multiple drop-down select menus that fetch data from an Axios request. The select only responds when I click the submit button. I want it to update when the user makes a selection from the drop-down. I can ...

An issue is preventing the Angular 2+ http service from making the requested call to the server

I am looking to create a model class that can access services in Angular. Let's say I have the following endpoints: /book/:id /book/:id/author I want to use a service called BooksService to retrieve a list of Book instances. These instances should ...

"Encountered an error while trying to define a Boolean variable due

I am in the process of creating a TF2 trading bot with price checking capabilities. I encounter an issue while trying to define a boolean variable to determine if the item is priced in keys or not. My attempt at replacing isKeys with data[baseName].prices ...

What is the method for initializing fancybox with the precise image index?

My current usage of fancybox involves the following code snippet: $("a[rel=Fancy" + UserId + "].Items").fancybox({ 'autoscale': 'false', 'cyclic': true, 'transitionIn': 'elastic', & ...

Maintaining sequential order IDs for table rows even after removing records

I currently have a table structured as follows: <table> <tr> <td> <input type="hidden" name="help[0].id" /> </td> <td> <span class="tr-close">X</span> </tr> <tr ...

Employing a break statement after the default case within a switch statement even when the default is not

According to a tutorial from w3schools that discusses switch statements, it is advised: If the default case is not the last case in the switch block, it is important to remember to end it with a break statement. However, the same tutorial also explains ...

Leveraging ng-change in AngularJS when utilizing the "Controller As" syntax

Attempting to steer clear of using $scope within my controller function, I have instead chosen to use var viewModel = this; employing the "controller as" viewModel syntax. The issue at hand is that while I can access data from a service, invoking functio ...

Utilizing hover effects and timeouts to conditionally show React components

I encountered a challenging React layout dilemma. It's not a complex issue, but rather difficult to articulate, so I made an effort to be as clear as possible. The data I have maps individual components in the following way: map => <TableRow na ...

Delete the final element from the json object

I need to extract the data from a JSON object and exclude the last element called 'week2'. Here is the object with properties like username, Geo, status, month, and week2: const response = [{ Username: "Denisse Morales", Geo: "NSU", ...

Make sure to include the onBlur and sx props when passing them through the slotsProp of the MUI DatePicker

This DatePicker component is using MUI DatePicker v6. /* eslint-disable no-unused-vars */ // @ts-nocheck import * as React from 'react'; import { Controller } from 'react-hook-form'; import TextField from '@mui/material/TextField&a ...

Customize the color of each individual column in DotNet.HighCharts by setting unique colors for

Can DotNet.HighCharts be used to create a chart where each column is a unique color? ...

Is there a workaround for retrieving a value when using .css('top') in IE and Safari, which defaults to 'auto' if no explicit top value is set?

My [element] is positioned absolutely with a left property set to -9999px in the CSS. The top property has not been set intentionally to keep the element in the DOM but out of the document flow. Upon calling [element].css('top') in jQuery, Firef ...

Is the XMLHttpRequest object closed once the response is received?

Attempting to establish a connection with the server using an XMLHttpRequest object to send data intermittently. The process of creating the object and establishing a connection is as follows: var xhr = new XMLHttpRequest(); xhr.open("post", location, tru ...

Utilizing the parameter "error" to distinguish unsuccessful tasks within async.mapLimit

In my coding process, I am using async.mapLimit to implement some parallel operations on an array with a limit of 10: async.mapLimit(files, 10, function(file, callback) { ... etc... }, function(error, files) { ... etc.. }); Within the main opera ...

Angular Starter Kit

When starting with Angular.js, there are various boilerplate kits available such as angular-seed, some incorporating requirejs and more. However, many of the options I've come across seem to be outdated. As a newcomer to Angular, I'm wondering if ...

Exporting functions using reactjs and babel

I'm currently working on a project that involves using reactjs, transpiled by babel with es2015 and react transforms configured in my .babelrc file. In the initial stages of refactoring, I realized that many of the classes should actually be functions ...

What is the best way to execute a callback once a mongoose findOneAndUpdate operation has successfully completed

Within my API, I utilize the app.put method in Express to search for a document in a collection with a specific title using Mongoose's findOneAndUpdate method for updating. app.put("/articles/:articleTitle",(req, res) => { Article.fin ...

"Real-time image upload progress bar feature using JavaScript, eliminating the need for

I have successfully implemented a JavaScript function that displays picture previews and uploads them automatically on the onchange event. Now, I am looking to add a progress bar to show the upload status. However, all the solutions I found online are rel ...

Validation method in jQuery for a set of checkboxes with distinct identifiers

I am faced with a situation where I have a set of checkboxes that, due to the integration with another platform, must have individual names even though they are all interconnected. <div class="form-group col-xs-6 checkbox-group"> <label cla ...