Jess Uni
Jess Uni

Reputation: 37

JavaScript: TypeError: work.calls is not iterable in function decorator (beginner question)

I was trying to pass a function work into a decorator where it will save function calls in an array work.calls.

The function:

work(a, b) {
  console.log(a + b);
}

The decorator:

function decorator(func) {
  func.calls = [];
  return function(...args) {
    func.calls.push(args);
    return func.call(this, ...args);
  }
}

Decorate function work:

work = decorator(work);

Calling new function:

work(1, 2); // 3
work(4, 5); // 9

for (let args of work.calls) {
  console.log( 'call:' + args.join() ); // TypeError: work.calls is not iterable
}

work.calls is an array so why is it not iterable?

For reference, there's another version of decorator written by someone else that actually works:

function decorator(func) {

  function wrapper(...args) {
    wrapper.calls.push(args);
    return func.apply(this, arguments);
  }

  wrapper.calls = [];

  return wrapper;
}

work(1, 2); // 3
work(4, 5); // 9

for (let args of work.calls) {
  alert( 'call:' + args.join() ); // "call:1,2", "call:4,5"
}

What does wrapper do here and why this way works?

Upvotes: 3

Views: 129

Answers (1)

Ram
Ram

Reputation: 144689

The 2 functions do not work the same. Your decorator defines the calls property on the passed function. The other decorator function sets the property on the nested and returned (wrapper) function. You get that error as work.calls is undefined. The decorated work refers to the anonymous function: return function(...args) which doesn't have calls property.

If you change work = decorator(work); to let work2 = decorator(work); then you will see that calls is set on the original work function and not on work2.

Your code works the same if you define the calls property on the returned function of the closure.

function decorator(func) {
  function __(a, b) {
    __.calls.push([a, b])
    return func(a, b)
  }
  __.calls = []
  return __;
}

Upvotes: 3

Related Questions