Pass more values to subscribe function of an observables - javascript

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

Related

how does the returned function of JavaScript debounce function get all parameters through arguments

generally for js debounce function, a simple implement goes like
function debounce(func, wait) {
let timerId = null;
return function (...args) {
console.log('args',args);
clearTimeout(timerId);
timerId = setTimeout(() => func.apply(this, args), wait);
}
}
and when you use it, you can do things like
var random = function() {console.log(arguments)};
var test = debounce(random, 1000);
test(1,2,3);
my question is, how does the returned function inside debounce function can get all attributes that gets passed into test function (here being 1,2,3) through arguments object? I feel like it might have to do with closure, but can anyone explain?
I've also created a jsFiddle for simpler view
https://jsfiddle.net/u4n07veb/22/
Another question would be in this js fiddle, my console.log args can print 1,2,3, since 1,2,3 is what I pass to test function, but would it also be possible to get 4,5,6 inside the debounce function? Since 4,5,6 is the parameters I pass to the callback function of the debounce function?
Though the arguments special object does exist in JavaScript, in the implementation of debounce above the function arguments are instead retrieved via a rest parameter.
The difference is minimal - arguments is not a true Array but instead an Array-like object, whereas the ...args rest parameter method will retrieve an actual Array - but it's one worth mentioning.
The actual passing through of these arguments happens when Function.prototype.apply is called, which allows a function to be called with a given value of this and a specified array of arguments. It's a sibling to the similar Function.prototype.call, which takes each argument to passed through as a separate parameter.
So in your example, you call test(1, 2, 3) and that executes the function that was returned by debounce(random, 1000). That function gets its arguments as an Array via the ...args rest parameter, and then passes that array through to the random function via func.apply(this, args).
To answer your question about passing a different set of parameters through, I recommend you try it and see. But the answer is yes: with this setup the debounced function is able to pass through any number of arguments.
Closures aren't directly relevant to how the arguments are passed through here. They are relevant in a different way, however, in that the timerId variable created when debounce is called is kept in a closure so that later attempts to call the debounced function will access it again, which is what allows the innards of this debounce implementation to clear the timeout it had created during its previous execution.

Cannot map bind over array

I was trying to solve https://twitter.com/secoif/status/730207047892017153 when I got an error message I don't understand. I get the error when running this code
const fns = [
function () {
console.log(1)
},
function () {
console.log(2)
},
function () {
console.log(3)
}
]
fns.map(Function.prototype.call.bind)
Chrome tells me "Bind must be called on a function", which I don't understand. The following line, which should be equivalent, does not throw the same error.
fns.map((x) => Function.prototype.call.bind(x))
To solve the JS pop quiz, you can do this:
for (var x in fns) fns[x]();
However, I realize that's not what you're asking :).
There are a few things I don't understand in your approach:
1) Why are you using .map()? Map is used for returning another array, which is not needed, so why not forEach() instead?
2) I'm not sure why you are using bind. When using map(), the callback is passed 3 parameters: the current function, the index of the function in the array, and the array itself. When you look at the syntax for bind(), you'll notice the first param for bind is the 'this' object, followed by the parameters to be passed in the function being bound to. In this case, 'this' will be set to the current function, index and array will be passed as parameters to the function.
3) Using bind on call. call() will take the same parameters a bind(), where the first one is the 'this' and the rest are parameters to be passed into the function being called. When you used .bind(), it will set the 'this' object as function and the first param will be the index. So from the perspective of .call(), you're setting it's 'this' to the function, and passing the index as the first param to call(), which is call's 'this', which then passes the whole array as the first parameter to the function.
Long story short, you're getting your values all mixed up and your overcomplicating this.
from the docs mdn docs for map
thisArg Optional. Value to use as this when executing callback.
Default value is the Window object
As marked before, you can map:
fns.map(Function.prototype.call.bind, Function.prototype.call.bind)
If you call:
fns.map(Function.prototype.call.bind)
Bind apply on object not function! and error raised, because object has not bind method.

Passing arguments to filter()

I have a function that takes an array and then a number of arguments. In that function I want to use the filter() method on the array, but I want to use arguments[i]. As an example:
function destroyer(arr) {
var test = arr.filter(function(value){
return value != arguments[1];
});
return test;
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
Calling arguments[1] there doesn't give me what I want, because I'm guessing the anonymous function has its own arguments that filter() is using. One solution I thought of was creating an array and copying the arguments to that. But I was wondering if there was a way to pass the arguments to filter? I know filter has a thisArg value, but I'm not sure how to use that to get what I want.
Edit: I have tried googling for a solution, and most of the solutions involve using bind or apply. However, I couldn't find anything directly related to filter() or similar methods like forEach, reduce, map, etc. I've tried using "this" as the thisArg value, but that doesn't seem to do anything. I've tried using bind and apply with it, but I can't seem to get the correct syntax for it.
If you have access to an ES6 environment, you can use "spread parameters" (...) to represent your variable-length argument list. Along with arrow functions, and Array#includes, your code could be quite compact:
function destroyer(arr, ...destroy) {
return arr.filter(value => !destroy.includes(value));
}
I have a working solution (with copying the arguments to an array), but I'm more interested in trying to figure how to pass the arguments to filter() to better understand how filter() and other similar methods work.
You don't need to pass the arguments to filter(). The arguments can simply be made available in the scope of the filter callback. However, arguments is special; it refers to the arguments of the current function. There is no way to refer to the arguments of some other function, such as the outer function. Therefore, there is no alternative but to save the arguments under a different variable name (such as args) in the outer function; then you can refer to that variable from within the callback, because the callback "closes" over that variable.
If you're really intent on "passing" the list of numbers to be destroyed to the inner function, instead of just letting it access them by referring to a variable created in the outer scope, then yes, in theory you could use thisArg:
function destroyer(arr/*, number to destroy*/) {
return arr.filter(do_not_destroy, arguments);
^^^^^^^^^ PASS thisArg
}
Now we can define do_not_destroy as
function do_not_destroy(elt) {
return [].slice.call(this, 1).indexOf(elt) === -1;
^^^^ REFER TO thisArg (arguments)
}
Note two things here. First, we refer to the list of arguments as this, because we passed them to filter using thisArg. Second, we cannot write this.slice..., and instead must write [].slice.call(this... , because this is an arguments object, which is not a "real" array and does not have the slice method.
To make do_not_destroy independent of the fact that its parameter is a special arguments object, with an extra parameter (the array) at the beginning, we could do the conversion to an array within the destroyer function:
function destroyer(arr/*, number to destroy*/) {
return arr.filter(do_not_destroy, [].slice.call(arguments, 1));
}
Now we can define do_not_destroy as
function do_not_destroy(elt) {
return this.indexOf(elt) === -1;
}
But now we are basically back to where we started. Instead of "passing" the list of values to be omitted by merely letting the inner function (callback) refer to a variable set in the outer scope, we are passing them to the callback using the back-door thisArg mechanism. I don't really see the point in this.

Passing an array of arguments

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.

Advanced parameter usage

//This is the function that will run every time a new item is added or the
//list is sorted.
var showNewOrder = function() {
//This function means we get serialize() to tell us the text of each
//element, instead of its ID, which is the default return.
var serializeFunction = function(el) { return el.get('text'); };
//We pass our custom function to serialize();
var orderTxt = sort.serialize(serializeFunction);
//And then we add that text to our page so everyone can see it.
$('data').set('text', orderTxt.join(' '));
};
full code is at http://demos.mootools.net/Dynamic.Sortables
var serializeFunction = function(*el*) { return el.get('text'); };
var orderTxt = sort.serialize(serializeFunction*(el)*);
compare the codes.
Is el being passed or not? what is going on???
I want to learn advanced parameter usage.
If not declaring functions like function name(parameter1, parameter2, parameter3...).
If not calling functions like name(parameter1, parameter2, parameter3...).
If parameters aren't variables.
If declaring functions like function(parameter1, parameter2, parameter3...).
If calling functions like variable(parameter1, parameter2, parameter3...).
If parameters are objects.
I'm interested.
You probably have a bookmark with the lessons in which I'm interested... please, share!!!
The value assigned to "serializeFunction" is actually an anonymous function, you can see it like a pointer or reference to a function, "el" is simply a declared input parameter that will be used then that function will be called.
Looking at the original code of the one that was posted, the call of the sort.serialize function, receives only the function as a parameter, the "serializeFunction" is not being invocated, it's only passed as an argument.
So, the serialize function that receives the reference of the function passed as a parameter it will be in charge of execute it internally.
This is a lambda expression like.
sort.serialize()
accept the function as parameter, not the value.
The first code is probably correct.
In JavaScript, functions are stored in variables just as any other value (as you see with serializeFunction), and sort.serialize only takes a reference to serializeFunction. Then serializeFunction is called from sort.serialize with the current element (el).
The second code would send an undefined value to the serializeFunction (since el has not been defined in that scope) which would throw an error. Even if el was defined, sort.serialize expects a reference to a function, not a value.

Categories

Resources