0

I took this code from the Functional Programming with JavaScript Using EcmaScript 6 book.

This is how the code works. Calling doPayment() multiple times don't execute the input arrow function () => { say("Payment Done") } due to the internal variable done is set to true in the first run.

But my understand is that when doPayment() is called every time, the variable done will be initialized with false every time, so the internal arrow function will run every time.

How is it working?

function say(v)
{ 
    document.querySelector('#out').innerHTML += v + "</br>";
}

const once = fn => {
  
  let done = false;
  
  return function() {
    return done ? undefined : ((done = true), fn.apply(this, arguments));
  }
}

var doPayment = once(() => {
  say("Payment Done");
});

doPayment();
doPayment();
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
 <span id="out" style="font-family: roboto"></span>
</body>
</html>

--------- UPDATE --------------------

It's so discouraging to see how a moderately complex problem like this one is asked to be closed because it is a duplicate of some other question.

Among all the answers, @Sylvester's answer is chosen as the right answer. I have also given my own explanation as the answer.

wonderful world
  • 10,969
  • 20
  • 97
  • 194
  • Possible duplicate of [How do JavaScript closures work?](http://stackoverflow.com/questions/111102/how-do-javascript-closures-work) – Siguza Apr 09 '17 at 11:51
  • 2
    off topic: I'd use something more like `const once = fn => (...args) => fn? fn.apply(fn=null, args): undefined;` where `fn` itself doubles as the flag. The main advantage is not that you don't need this extra variable, but that you stop referencing the `fn` so it may be GC and thus avoiding a potential memory leak. Better `const noop = () => undefined, once = fn => typeof fn === "function"? (...args) => fn? fn.apply(fn=null, args): undefined: noop;` because I don't trust the type of `fn` – Thomas Apr 09 '17 at 12:11

4 Answers4

2

But my understand is that when doPayment() is called every time, the variable done will be initialized with false every time

The line let done = false is in the body of once, so it executes when once is called. The body of doPayment is merely return done ? undefined : ((done = true), fn.apply(this, arguments));, so that's the only thing that is executed when you call doPayment.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • 2
    In other words, `doPayment()` isn't calling `once()`, it's calling the function returned by `once()`, and `let done = false` is called only in (scope with) `once()`. – Jared Farrish Apr 09 '17 at 12:07
  • Both the answer and the comment have to be put together to make the right answer. Still, how the ```done``` is mutated and it's scope are not explained. – wonderful world Apr 09 '17 at 20:06
2

When you call once a local variable done is initialized to false and a function is returned. That function is the one which is bound to doPayment and thus each invocation checks and perhaps mutates done created in the once invocation that created that very function.

If you were to make two:

const fnPrint = console.log.bind(null, "test");
const fn1 = once(fnPrint);
const fn2 = once(fnPrint);

Here fn1 and fn2 are from two different invocations of once and thus they will have different done binding in their closure.

fn1() ; prints "test"
fn1() ; does nothing
fn2() ; prints "test"
fn2() ; does nothing
Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • 1
    Misspelling in the first paragraph. – Jared Farrish Apr 09 '17 at 12:08
  • 1
    @wonderfulworld `once` gets executed that one time where you are binding `doPayment` so your statement about `once` never being executed is false. Both `fn1` and `fn2` in my example are results from running `once` but calling them will be calling only the function `once` returned and not `once` itself. – Sylwester Apr 09 '17 at 20:28
1

Your once function is comprised of two function, the first returns the second.

The first function will be executed when you call it initially with your callback function that should only run once. When this happens you are setting the done value to false and returning the second function.

Now when you call your original function you are no longer executing it directly, instead you are calling the function returned from once (second function).

Here you are looking for a variable assigned to done. there is no variable of the name in the scope so the interpreter will look up to the parent scope which is the first function where it will find the variable that it can use in the second function.

return done ? undefined : ((done = true), fn.apply(null, arguments)) 

what that statement above is doing is if the done variable is truthy return undefined else, set the done variable to true, then call the decorated function with the given context and arguments.

for more look up closures and lexical scoping

synthet1c
  • 6,152
  • 2
  • 24
  • 39
  • The second paragraph is incorrect in my opinion. Please look at the other answers and my comments. The function ```once``` is not executed, it is a function alias. What really executed is ```return done ? undefined : ((done = true), fn.apply(this, arguments));```. – wonderful world Apr 09 '17 at 20:17
-1

There is a few JavaScript language features in this example. They are (1) closure (2) comma operator (3) high order functions.

The method say is for just display purpose only, so don't have to considered as part of the problem. once is a constant function when executed will return another function. The doPayment is the high order function.

The ((done = true), fn.apply(this, arguments)); uses a comma separated operator. The first expression done = true is executed first and then the second fn.appy is executed next and return the results from the second function.

When the JavaScript execution engine runs the first doPayment(), it runs the following:

var doPayment = once(() => {
  say("Payment Done");
});

It runs the once method. At that time done = false is executed. Then the inner function is returned and that is assigned to the variable doPayment. The inner function is:

done ? undefined : ((done = true), fn.apply(this, arguments));

Since the above is inside the once function, it has created a closure with the variable done.

Now when the doPayment (second last statement) is run , so a check is done against done which evaluates to false. The expression ((done = true), fn.apply(this, arguments)) gets executed. This will set the done to true and executes fn.apply.

Now when the doPayment is called again (the last statement), the inner function

done ? undefined : ((done = true), fn.apply(this, arguments));

gets executed. This inner function has already a closure which has done set to true already by the previous execution. So it returns undefined.

When the above code is run, the following functions are run:

  1. once (after the run, done is false)
  2. done ? undefined : ((done = true), fn.apply(this, arguments)); (after the run, done is true)
  3. done ? undefined : ((done = true), fn.apply(this, arguments)); (after the run done stays as true
wonderful world
  • 10,969
  • 20
  • 97
  • 194