Passing an array of arguments - javascript

I'm trying to have a function that is passed a function and calls it with a set of args. For example:
function foo(func, args) {
return func(args);
}
However, I don't know how many elements might be in args, and func should be able to be an arbitrary function that takes any number of args. How do I deal with this?
Also say I wanted to store the functions passed to foo and only call them if they hadn't been called before.
Can I just do something like:
var calledFuncs = [];
function foo(func, args) {
if(calledFuncs.indexOf(func) === -1) {
calledFuncs.push(func);
return func(args);
}
}
Thanks, I'm a bit new to functional programming in JavaScript.

You're looking for func.apply:
the first argument is the context aka this value. You don't want to change this, so pass it through.
the second argument is an array containing the arguments, which can of course be of dynamic length.
So you could do this:
return func.apply(this, args);
You seem to have a fine approach to your second issue (with the func(args) replaced). You may want to store the return value of called functions, though, since foo now returns undefined for any calls except the first.

Use apply:
func.apply(this, args);
apply takes an array of arguments as the second argument.
The first argument to apply will be the value of this in the scope of the function that you are calling. So you can pass in the current this or anything else that you want.
As far as your second question goes, that will only work for named functions. You can use func.name to store the function name in your array:
var calledFuncs = [];
function foo(func, args) {
if(calledFuncs.indexOf(func.name) === -1) {
calledFuncs.push(func.name);
return func(args);
}
}
This won't work for anonymous functions and it doesn't work for named functions in IE or Opera. You're going to have to parse it out, perhaps like so:
var name = func.toString().replace(/\n/g, "").replace(/\s*\(.*$/, "").replace(/^\s*function\s+/, "");
As far as your second question goes, you can do what you're doing right now. But I don't think it would work for the following case:
foo(function() {
}, []);
foo(function() {
}, []);
It will call both of those functions.

You want Function.prototype.apply:
func.apply(this,args);
Set the context (the first argument) to whatever you want, including null to get the window global (as you would get with your current func(...) invocation.
Although not directly related to your question, see also the related Function.prototype.call method that allows you to similarly set the context, but pass explicit parameters.

Related

Pass more values to subscribe function of an observables

I would like to convert and replace all words in an array using a an object's method which returns an observable. The problem is that since the result is asynchronous, the value of result is overwritten incorrectly at the wrong index. If I had a synchronous function, I could have just simply call the function in a for loop. What should I do in this case?
for(let word in this.words){
var i = Object.keys(this.words).indexOf(word);
this.convertor.get(this.words[i].value).subscribe( (result) => {
this.words[i].value = result; //The value of i is incorrect
});
}
Since ES6 you can easily solve this by replacing var i = with let i =. That way you get a variable that has block scope, and so you get a different variable instance in each iteration of the loop.
The original answer works also when you don't have support for let:
You could solve this with a plain function that you bind an extra argument to:
this.convertor.get(this.words[i].value).subscribe( function (i, result) {
this.words[i].value = result;
}.bind(this, i));
.bind() returns a (new) function that will be like the function you apply bind on, but will pre-determine a few things for when that function is actually called:
this will then be set to what you pass to bind in the first argument. Contrary to arrow functions, old-fashioned functions normally get their this value by how the function is called -- that behaviour is not what you desire, and so it is a welcome feature of bind;
Some of the arguments can be fixed upfront. Whatever other arguments you pass via bind become the values of the first arguments of that function -- they are not determined by the actual call. In this case the value of i is passed, so the function will get that value as its first argument, no matter how it is called later.
Any other argument that is passed to the function at the moment of the call(back) will follow the previously mentioned arguments. In this case, the actual call will pass the value of result, and so the function should deal with two arguments: one provided by bind, the other by the caller.
This solves your issue as you can pass on the correct value of i to each of the functions without losing the value of this.
Another alternative could be forEach so you don't have to track the index and handle cleaning up the subscriptions with first() for example.
this.words.forEach((word, i) => {
this.obs$(word)
.pipe(
tap(v => (this.words[i].value = v)),
first()
)
.subscribe();
});
https://stackblitz.com/edit/static-iterable-value-from-obs?file=src/app/app.component.ts

Executing a method inside a method

I'm currently working on a JavaScript exercise in FreeCodeCamp, and one of the test-cases my code should work with is a function call that looks like this:
addTogether(2)(3);
here is the bare-bones function I'm given:
function addTogether() {
return;
}
When I run the code below:
function addTogether() {
return arguments;
}
In the console, that the editor provides, I get:
TypeError: addTogether(...) is not a function
The instructions hint at using the arguments object, and it works well with test-case function calls that only have one argument object (i.e. addTogether(2, 3);), but not with the one I've shown above.
Is there a way to access/utilize the separate argument objects when they're in the format I showed above?
Note: I don't want any sort of answer to solve the problem, just info on any techniques on accessing the arguments of these type of function calls.
Help, is greatly appreciated.
Don't think of it as two separate sets of arguments. Think of it as you're calling another function (which you are). Functions are first-class values in JavaScript so you can use them just like any other value. This includes returning them from functions.
var f = function(y) {
console.log('f:', y);
};
var getF = function(x) {
console.log('getF:', x);
return f;
};
getF(1)(2);
Functions can also use values that exist in any parent scope. Loosely speaking, functions which do this are called closures.
function createGetX(x) {
return function() {
// Since x is in the parent scope, we can use it here
return x;
}
}
var one = createGetX(1);
console.log(one()); // Always
console.log(one()); // returns
console.log(one()); // one
Sometimes it helps me to think in a certain order. You can read the code "addTogether(2)" and stop before reading the "(3)".
From there you can see the exercise wants you to have that first part "addTogether(2)" return a function...since anytime there are "()" that means a function is getting called.
So "addTogether(2)" needs to return a function that takes one argument. Later that 3 will be one example of an input.
I think the name "addTogether" is a bit confusing..since that function's job is to make up and return the actual function that adds.
Sort of hard to explain this one without helping too much, but the bulk of your job here is to return a custom function, which includes the first variable (from it's scope) and expects another variable when it's called.
You could do this with closures.
function addTogether(x) {
return function(y) {
return x+y;
}
}

Are function-arguments not necessarily objects?

I'm learning functional programming and node.js, and I came across this odd problem when using Function.prototype.apply and .bind.
function Spy(target, method) {
var obj = {count: 0};
var original = target[method]
target[method] = function (){//no specified arguments
obj.count++
original.apply(this, arguments)//only arguments property passed
}
return obj;
}
module.exports = Spy
This code works, it successfully spies on target.method.
//same code here
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
//same code here
This code, however, does not. It gives an error message: TypeError: CreateListFromArrayLike called on non-object.
And then the biggest surprise is, this method works perfectly fine.
//same code here
target[method] = function (args){
obj.count++
original.bind(this, args)
}
//same code here
So why exactly do I get this error? Is it because function arguments are not necessarily objects? Or is it because apply has a stricter description than bind?
In this version:
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
Here you are not taking all the arguments but just one, named args. Since apply expects an array like object you cannot use args since it is only the first argument passed to the original target.
You can change it to:
target[method] = function (arg){ //only one argument specified
obj.count++
original.apply(this,[arg]) //one argument passed here
}
Now it works, but you can only spy on one argument functions. Using call would be better since you only have one extra argument:
target[method] = function (arg){ //only one argument specified
obj.count++
original.call(this,arg) //one argument passed here
}
Now bind is a totally different animal. It partial applies functions, thus return functions. Imagine you need to send a callback that takes no arguments but calls a function with some arguments you have when making it. You see code like:
var self = this;
return function() {
self.method(a, b);
}
Well. bind does this for you:
return this.method.bind(this, a, b);
When calling either of these returned functions the same happens. The method method is called with the arguments a and b. So calling bind on a function returns a partial applied version of that function and does not call it like call or apply does.
bind is called the same way as call is, even though they do very different things.
If you really wanted to use bind in this way. You could use the spread operator (ES2015) to expand the arguments 'array' to individual arguments:
original.bind(null, ...args);
That will bind the original function with the array values as individual arguments.

Passing a function as an object property

This is what I would like to do:
function genTxnId(id) {
return id;
}
var ws;
ws = new funcB('A', {evaluator: genTxnId(25)});
But I find that once it goes into funcB, the "evaluator" property of the passed in object is evaluated before going into the function. Is there anyway to keep it as genTxnId(25) until it is used within funcB.
I know that doing this would keep it as a function:
var funcA = function(b) { return b;
Would keep it as a function but then I won't be able to pass in argument b.
Using .bind(), you can bind the parameters of the function to specific values without calling it. .bind() will return a function that you can then call elsewhere, passing in the remaining arguments that haven't been bound.
function getTxnId (id) {
return id;
}
function funcB (str, obj) {
obj.evaluator();
}
var ws = new funcB('A', {
evaluator: genTxnId.bind(this, 25)
});
The one caveat is that bind takes as its first parameter the object to which this will be bound. This means that you can't depend on the value of this in getTxnId to refer to the newly created object, which may seem more intuitive (albeit impossible, to my knowledge). But in general, you won't need to do that, so you can pass in any old object. You could even pass null if you won't ever use this inside that function, i.e. getTxnId.bind(null, 25).

When passing a callback function, can you set a parameter?

I am passing a callback function as a parameter to a function, and I am doing this in various parts of my web app.
I want the callback to react a little differently in some cases, can I pass a parameter somehow to this callback?
soemethod(callback);
otherethod(callback);
otherother(callback(a=1));
How could I pass a=1 in the callback?
Simply use an anonymous function wrapped around your parameterized function call:
otherother(function () {
callback(1); // assuming the first parameter is called a
});
No, you can't.
But you can do something like this:
soemethod(callback);
otherethod(callback);
otherother(callback, 1);
function otherother(callback, defaultValue) {
var value = defaultValue;
// your logic here, ie.
if(someCondition)
value = 2;
callback(value);
}
As others already mentioned, you can't pass default parameters like that in Javascript - you have to create the separate function yourself.
What you can do however, is use some very neat helper functions to create these closures automatically for you. One of the patterns I like the most is partial function application, where the "default" parameters are the leftmost parameters.
If you are using a new browser you can use Function.prototype.bind (it also handles the this parameter - this can allow passing methods as callbacks as well)
otherother( callback.bind(undefined, 1) );
//sets the first parameter to 1
//when the callback is called, the 2nd, 3rd, parameters are filled and so on
If you need to support older browsers as well, it is not hard to create your own partial application functions (lots of JS frameworks have one of some sort, the next example was taken from Prototype)
Function.prototype.curry = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
return fn.apply(this, args.concat(
Array.prototype.slice.call(arguments)));
};
};

Categories

Resources