0

I am playing with JS features to improve my understanding of the language.

In below example, I was trying to use call method to initiate Array function on given array object. I know alternatives to get the job done; however, I am curious why the code I have written does not work?.

Thank you!

Code

const f_timeit = (f) => {
  return function(...args) {
    util.p(`Entering function ${f.name}`);
    const start_time = Date.now();
    try {
      return f(...args);
    } finally {
      util.p(`Exiting ${f.name} after ${Date.now() - start_time}ms`);
    }
  }
};

const rarr = [...Array(10000000)].map(e => ~~(Math.random() * 20));

// following works
console.log(f_timeit((arr) => {
  return arr.reduce((acc, val) => acc + val, 0);
})(rarr));

// following fails
util.p(f_timeit(Array.prototype.reduce.call)(rarr, (acc, val) => acc + val, 0));

Error:

        return f(...args);
               ^
TypeError: f is not a function
7
  • The call method is not bound to the reduce function. You may as well have used f_timeit(Function.prototype.call), same thing. The error message is very misleading btw, it's not that f is not a function but rather that call was invoked on something that is not a function. Commented Nov 18, 2022 at 4:46
  • "I know alternatives to get the job done" - what solutions do you have in mind? Commented Nov 18, 2022 at 4:47
  • @Bergi I meant the arrow function solution I put above. Btw, what would be arguments of that call method if I call as you suggested; how i will specify to call Array.prototype.reduce? Commented Nov 18, 2022 at 5:05
  • You can use call.bind(reduce). Another (better) solution is to use f.apply(this, args) in f_timeit, then f_timeit(Array.prototype.reduce).call(rarr, …, 0). Commented Nov 18, 2022 at 6:17
  • This has issue still f_timeit(Array.prototype.reduce).call(rarr, (acc, val) => acc + val, 0) Commented Nov 18, 2022 at 16:41

1 Answer 1

2

The error message is a bit misleading, it's not that "f is not a function" but rather that f is the Function.prototype.call method that was invoked on something that is not a function - in particular undefined. This is due to how the this argument works, and in particular because accessing methods on an object does not automatically bind them.

So various ways to achieve what you want are

f_timeit(() => rarr.reduce((acc, val) => acc + val, 0))()
f_timeit((ctx) => ctx.reduce((acc, val) => acc + val, 0))(rarr)
f_timeit((ctx, reducer) => ctx.reduce(reducer, 0))(rarr, (acc, val) => acc + val)
f_timeit((ctx, reducer, init) => ctx.reduce(reducer, init))(rarr, (acc, val) => acc + val, 0)
f_timeit((ctx, ...args) => ctx.reduce(...args))(rarr, (acc, val) => acc + val, 0)
f_timeit((ctx, ...args) => Array.prototype.reduce.apply(ctx, args))(rarr, (acc, val) => acc + val, 0)
f_timeit((ctx, ...args) => Array.prototype.reduce.call(ctx, ...args))(rarr, (acc, val) => acc + val, 0)
f_timeit((...args) => Array.prototype.reduce.call(...args))(rarr, (acc, val) => acc + val, 0)
// f_timeit(Array.prototype.reduce.call)(rarr, (acc, val) => acc + val, 0) - no!
f_timeit(Array.prototype.reduce.call.bind(Array.prototype.reduce))(rarr, (acc, val) => acc + val, 0) // yes!
f_timeit(Function.prototype.call.bind(Array.prototype.reduce))(rarr, (acc, val) => acc + val, 0)

However, I would generally recommend that you change f_timeit to respect the this value passed when the returned function is called:

const f_timeit = (f) => {
  return function(...args) {
    util.p(`Entering function ${f.name}`);
    const start_time = Date.now();
    try {
      return f.call(this, ...args);
//                  ^^^^
      return f.apply(this, args); // or equivalently
    } finally {
      util.p(`Exiting ${f.name} after ${Date.now() - start_time}ms`);
    }
  }
};

That way, you can use

f_timeit(Array.prototype.reduce).call(rarr, (acc, val) => acc + val, 0)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.