Please Give Me Some Closure

Closures and Promises (TS)

Closures

function outer(): () => void {
    let x: string = 'someSpecialData'
    function inner(): void {
        console.log(x)
    }
    return inner
}

A closure is when we use an outer function to seal/encapsulate some state that is available to the inner function to use, whenever the outer function is called. This took me a while to understand so best to go through the basics.

Lets say we have a function:

function SayHelloInTheConsole(){
    console.log("hello")
}

Lets say we then assign this function to two variables:

const sayHello = SayHelloInTheConsole
const sayHelloFirst = SayHelloInTheConsole()

You'll notice in your console that sayHelloFirst has given us our first hello in the console. Adding the parenthesis () during assignment calls the function and assigns the return to sayHelloFirst. However, sayHello has just been assigned the function it self without being called. so sayHello is essentially a duiplicate of the SayHelloInTheConsole function which can now be called at leisure via the button below.

Ok so that's that. Difference between assigning the return of a function and the function it self. Interestingly, we can log both the variables sayHello and sayHelloFirst like so: console.log('sayHello:', sayHello, 'sayHelloFirst:', sayHelloFirst), and as expected we get:

sayHello: ƒ SayHelloInTheConsole() {
    console.log("hello")
}
sayHelloFirst: undefined

Why is sayHelloFirst undefined? because it has been assigned the return of the fucntion SayHelloInTheConsole. If you look back, there is no return hence why sayHelloFirst is undefined. However, sayHello is assigned the function itself, ready to be called at will.

ok let's move back to our original outer function

function outer(): () => void {
  let x: string = 'someSpecialData'
    element.innerHTML = x

    function inner(): void {
      console.log(x)
    }
  return inner
}

We can assign this to a variable like so:

const functionToCallAtAnyPoint = outer()

Right, so now that we know that assigning a variable a function with parenthesis gives us the return of that function, what will the variable functionToCallAtAnyPoint hold?

It will hold what is returned from the outer function. That is:

function inner(): void {
  console.log(x)
}

So note - you shouldn't see any console of "someSpecialData" yet because we haven't called the return of outer yet. When we call functionToCallAtAnyPoint via the button below, we run the return of outer, which was the function named inner.

Then you might say, where is the inner function getting the value of x from? Ah - and therein lies the rub.

The x value has been encapsulated in the variable functionToCallAtAnyPoint because of the nature of closures. It was closed within the outer function. This x value is out of reach within the scope only of the outer function, and can be access at any point by calling functionToCallAtAnyPoint.

Take this problem as a challenge which I asked GPT to generate for me.

Create a function in JavaScript that takes another function f and a time t as parameters and returns a new function. This new function, when called, delays the execution of f by t milliseconds.

This was my first attempt:

 export function delayFunctionCall( funcToBeDelayed: () => void, time: number): void {
    const timeDelay: number = time
    function inner() {
        const promiseToCallFunctionInTime = new Promise((resolve, reject) =>  {
            setTimeout(() => { resolve() }, timeDelay) })
            promiseToCallFunctionInTime.then(funcToBeDelayed) }

    return inner()
}

Problem with the above is that we're asked to return a function, but according to the end of line one we're only returning void for one.

Notice the subtle of subtleties here! I return inner()!

That is, I return the calling of inner. And so inner actually gets called and the function inner executes, thus starting the whole promise cycle. Revised:


export function delayFunctionCallFixed(
    funcToBeDelayed: () => void,
    time: number
): () => void {
    return function inner() {
        const promiseToCallFunctionInTime = new Promise<void>((resolve, reject) => {
          setTimeout(() => {resolve()}, time)
        })
        promiseToCallFunctionInTime.then(() => funcToBeDelayed())
  }
}

The difference between these two functions is as follows.

const IwouldLikeYouCallYouLaterButYouWontListen = delayFunctionCall(
    () => console.log('function call 2s later starting now '),
    2000)

const dontRunTheDelayedFunctionTillThisIsCalled = delayFunctionCallFixed(
    () => console.log('function call 2s later at your request'),
    2000)

IwouldLikeYouCallYouLaterButYouWontListen has already run because it already inherently called inner() when I returned it by calling delayFunctionCall in the assignment to the IwouldLikeYouCallYouLaterButYouWontListen variable.

However, dontRunTheDelayedFunctionTillThisIsCalled has not. We've returned what we need from delayFunctionCallFixed, with the timer and promise all set to delay our passed in function call till we actually call dontRunTheDelayedFunctionTillThisIsCalled.

One More Thing Jackie

If you wanted to shorten it into nice arrow syntax we could write as follows:


const delayFunctionCallSuperSaiyen = (funcToBeDelayed: () => void, time: number): () => void => () => {
new Promise<void>((resolve) => setTimeout(resolve, time)).then(funcToBeDelayed)
};

Broken down: We have the two params, funcToBeDelayed and time. We have the return type which is a ()=>void, a function that returns void.

The key bit here is the returned arrow function after return type.

The return type which states a function that returns void, IS, the ()=>{...} that follows it. The => ()=>{} is just a shorthand way of returning another function.

bisous