I have to rewrite (a simpler version of) the ‘invoke’ method from the Underscore library for an assignment, which accepts a method’s name as a parameter. So far all I’ve found is that the () should be removed in order to pass a method as an argument without calling it.
So I’ve been trying a few things but all of them yield different results and errors, and I’d be grateful if anyone could help me understand what’s going on. I’m not looking for the answer to the whole assignment, rather hints at why I’m getting those results or which syntax or even concept I’ve got wrong. I’d like to get to the solution by myself thanks to those hints.
(I’ve had a look at this but 1. watching the ‘methodName’ variable in the debugger shows ‘f parseInt()’, ‘f join()’ (so… not strings), and 2. my code seems to work - at least partially - without using strings, so I’ve left this option aside so far.)
Ok… first of all, (also a simpler version of) the ‘each’ method, that I previously had to rewrite as part of the same assignment, and that I’m trying to reuse to implement ‘invoke’. It passes all the tests provided by my coding school to check against.
_.each = function (collection, iteratee, context) {
let keys = Object.keys(collection);
if (Array.isArray(collection)) {
for (let i=0; i<keys.length; i++) {
iteratee.call(context, collection[keys[i]], i, collection);
}
}
else {
for (let i=0; i<keys.length; i++) {
iteratee.call(context, collection[keys[i]], keys[i], collection);
}
}
return collection;
};
Now here are the instructions for ‘invoke’…
// _.invoke(collection, methodName, *arguments)
// Returns an array with the results of calling the method indicated by methodName on each value in the collection.
// Any extra arguments passed to invoke will be forwarded on to the method invocation.
…and my three attempts at it and the results in the Dev Tools console. The first one seems to work fine with parseInt but not with join:
function invoke1 (collection, methodName) {
let resArray = [];
_.each(collection, el => resArray.push(methodName(el)));
return resArray;
}
console.log(invoke1(['1','2','3'], parseInt)); // [123]
console.log(invoke1([['a','b','c'], ['d','e','f']], Array.prototype.join)); // Uncaught TypeError: Cannot convert undefined or null to object - expected ['a,b,c', 'd,e,f']
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js"></script>
With the second one, it’s the other way round:
function invoke2 (collection, methodName) {
let resArray = [];
_.each(collection, el => resArray.push(methodName.apply(el)));
return resArray;
}
console.log(invoke2(['1','2','3'], parseInt)); // [NaN, NaN, NaN] - expected [123]
console.log(invoke2([['a','b','c'], ['d','e','f']], Array.prototype.join)); // ['a,b,c', 'd,e,f']
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js"></script>
On the third one, I tried to implement the forwarding of arguments and with that, removing join’s default separating coma.
function invoke3 (collection, methodName, ...arg) {
let resArray = [];
_.each(collection, el => resArray.push(methodName.apply(el, ...arg)));
return resArray;
}
console.log(invoke3(['1','2','3'], parseInt)); // [NaN, NaN, NaN] - expected [123]
console.log(invoke3([['a','b','c'], ['d','e','f']], Array.prototype.join, '')); // Uncaught TypeError: CreateListFromArrayLike called on non-object - expected ['abc', 'def']
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js"></script>
Your example contains 2 different non-compatible cases: in case of parseInt
you pass an item as an argument, but in case of Array::join()
you pass an item as this
. You need different logic for the cases.
There are several options to handle those cases, one of them is to check whether an element contains the method and apply it (use args
instead of ...args
, otherwise use call
, not apply
):
const invoke3 = (collection, method, ...args) =>
collection.map(el => el[method.name] === method ? method.apply(el, args) : method(el, ...args));
console.log(invoke3(['1','2','3'], parseInt));
console.log(invoke3([['a','b','c'], ['d','e','f']], Array.prototype.join, ''));