Composing Callbacks and Functions in JavaScript
1. What Is a Closure in JavaScript
"Closure is when a function remembers (captures) the variables around it even when that function is executed elsewhere."
2. What Is a Callback in JavaScript
- is when you pass a function as a parameter
- you can define a behavior and pass it to another function to keep control of what happens when you call the callback
3. Can a Function Be a Closure and a Callback?
- YES.
- a callback is a function, so it will remembers the variables outside of it even you execute it in another scope
4. Compose Closures and Callbacks to Create New Functions
- think of compose as using a function call that returns a value as a parameter of another function that expects the same type of value as the firct function returns (hope that make sense!)
1let i = 02let callback = (event) => {3 return i++4}56let multiply = (value) => {7 console.log(value * 2)8}910document.addEventListener("click", (event) => {11 multiply(callback(event))1213 /*14 - callback is executed first15 - the result of the `callback` function call, is the input of the `multiply` function16 - finally `multiply` is executed17 */18})
- using
pipe
fromlodash
, helps to make the composed functions more readable, because you read in the order the functions will be executed. - using
compose
works the same aspipe
but with the oposite functions order - the result of
pipe
andcompose
is another function
5. Defining the Broadcaster and Listener Relationship
listener
is just a callback (a function you pass to something else)broadcaster
is a function that takes alistener
and calls it in different ways
1let listener = (value) => {2 console.log(value)3}45let broadcaster = (listener) => {6 listener(1)7 listener(2)8 listener(3)9}1011broadcaster(listener)
6. Time is a Hidden Variable in JavaScript
- operators help us to capture better behaviors and "control the future" in a more predictable way.
- when accepting listeners, you don't exactly know when those functions will be executed, maybe they have setTimeouts or setIntervals in them, so eventhough you execute them inmediately in the broadcaster, they may delay the whole exxecution using timeouts.
7. Create a Function to Configure setTimeout
- when using
setTimeout
, you can control when and with what is being called, by wrapping it in a function and accept all the parameters you can/need to control
1let createTimeout = (time) => (callback) => {2 setTimeout(callback, time)3}45let oneSecond = createTimeout(1000)6let twoSeconds = createTimeout(2000)7let threeSeconds = createTimeout(3000)89oneSecond(() => {10 console.log("one")11})12twoSeconds(() => {13 console.log("two")14})15threeSeconds(() => {16 console.log("three")17})
createTimeout
is a function that accepts a time variable, that returns another function that accepts a callback (closing over time), that callssetTimeout
8. Return a Function to Cancel an Async Behavior
1let createTimeout = (time) => (callback) => {2 const id = setTimeout(callback, time)34 return () => {5 clearTimeout(id)6 }7}89let cancelOne = createTimeout(1000)1011cancelOne() // cancel the timeout
- again, you are wrapping a piece of behaviour with a function, that way, you can control when and where that new function will be called (the cancel function)
9. Wrap addEventListener in a Function for More Control
1let createTimeout = (time) => (callback) => {2 let id = setTimeout(callback, time)34 return () => {5 clearTimeout(id)6 }7}89let addListener = (selector) => (eventType) => (listener) => {10 let element = document.querySelector(selector)11 element.addEventListener(eventType, listener)1213 return () => {14 element.removeEventListener(eventType, listener)15 }16}1718let addButtonListener = addListener("#button")19let addButtonClickListener = addButtonListener("click")20let removeButtonClickListener = addButtonClickListener(() => {21 console.log("Button clicked")22})2324removeButtonClickListener()
- You see how
addListener
is chaining functions and getting all the values it needs to create the actual listener? this for me is beautiful!!
10. Create a Utility Function to Control setInterval
1let createTimeout = (time) => (listener) => {2 let id = setTimeout(listener, time)34 return () => {5 clearTimeout(id)6 }7}89let addListener = (selector) => (eventType) => (listener) => {10 let element = document.querySelector(selector)11 element.addEventListener(eventType, listener)1213 return () => {14 element.removeEventListener(eventType, listener)15 }16}1718let createInterval = (time) => (listener) => {19 let id = setInterval(listener, time)20 return () => {21 clearInterval(id)22 }23}2425let oneSecond = createInterval(1000)26let cancelOneSecond = oneSecond(() => {27 console.log("one")28})2930cancelOneSecond()3132let twoSeconds = createInterval(2000)33twoSeconds(() => {34 console.log("two")35})
- this is very similar to the previous two lessons
- the pattern we are following here is:
- find the pieces you can strip out to control (listener, eventType, time...)
- return a function that can cancel the behavior you just create (clearTimeout or clearInterval)
16. Pass a Done Symbol when an Async Function is Done
- we can take advantage of the iterator protocol and return a "done" value when we reach the last item of our iterables
- you can check if the value passed to the broadcaster is "done" and you can prevent the listeners to continue executing after finish
17. Create an Operator Function to Modify Behaviors
- here you can see the power of functions wrapping other functions!
- we create a new listener that wraps the original and modify the behavior of the original listener by applying some changes before passing the value to it
18. Transform Values with a Map Operator
- operators are functions that accepts a modifier function and change the behavior of our broadcasters and listeners
19. Prevent Certain Values with a Filter Operator
- as you transform before, you can also filter values by passing a
predicate
function that executes a condition that returns a boolean value
20. Use Pipe to Clean Up Functions Wrapping Functions
- REFACTOR, REFACTOR, REFACTOR!
- compose works here because all the functions inside have the same signature (accept a broadcaster)
- remember that
pipe
is the same ascompose
but with a readable order of functions
21. Start With the API You Want Then Implement
- start with the API you want and then implement it to meet that API
- you can express what you prefer with your API, either what is really happening with the functions or what is happening in the browser (or in your particular environment)
22. Define a Function to Set Common Behaviors in Operators
- again, the magic of functions wrapping other functions!
28.
- TODO: ASK QUESTION ABOUT APPLYOPERATOR!!!
29. Create a Win Condition with a mapDone Operator
- show how we can add additional logic yo our previous play functions, by wrapping it and capturing all the cancel values to cancel when a condition is met (by filtering the results)