- A closure is just a function that accesses things outside of it
- It can capture anything outside of it and use that value each time the function is called
questions:
- shouldn't
i
increase to 1 when loggingi++
?
This is like a for loop, it wipes down the variable alignment.
What is a callback in javascript
According to MDN:Β "A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action."
- A callback is when you pass a function as an argument to another function
- Function passed into another function as an argument
- Invoked or "called-after" inside the outer function to complete an action/routine
- Can be synchronous or asynchronous # Can a Function be a Closure and a Callback?
We can create a function that can be both
- A closure
- A callback
let i = 0
let callback = (event) => {
console.log(i++)
}
let anotherFunction = (fn) => {
fn()
fn()
fn()
}
anotherFunction(callback)
We can see that callback
is being passed to anotherFunction
making it
- a callback because is being passed as an argument to another function
-
a closure because it's capturing the variable
i
outside of itCompose closures and callbacks to create new functions
Composition - a function passing one value to another function
instead of doing:
let value = callback()
multiply(value)
we can pass callback inside multiply straight away:
multiply(callback())
Cleaner ways to compose functions:
- using
pipe
fromlodash/fp
- using
compose
fromlodash/fp
example
// multiply(callback()) becomes
pipe(callback, multiply)
// multiply(callback()) becomes
compose(multiply, callback)
compose allows you to refactor the code by removing nested functions and instead list them out in order.
With pipe, each return is passed to the next function in line.
With compose the return is passed to the previous function in line - so the value returned to the callback will be passed to multiply.
Using pipe vs compose
We're going to stick to using pipe, which takes the functions in a different order, because when thinking asynchronously, it makes more sense to think, "Well, the callback is called, and then multiply is called." We're sticking to this.
Defining the broadcaster and Listener Relationship
There are two concepts that will be used through this*listener* and broadcaster. This concepts holds a strong relationship.
This are common names used to define the behavior of this functions.
- listener refers to a callback function, a function meant to be passed as argument to another function
- broadcaster refers to a function that accepts a Listener (a callback) and triggers it to perform certain task.
In this scenario, the broadcast function is the one that initiate the data flow. The broadcaster starts the communication performing a task and calling the listeners asking them to perform certain task.
π In general words, the listeners function are not meant to be used directly.
let listener = value => {
console.log(vale)
}
let broadcaster = callback => {
callback(1)
callback(2)
callback(3)
}
broadcaster(listener)
Time is a Hidden Variable in javascript
Another type of function that we will often use is call operator An operator is just a function that calls the broadcaster but in some particular way.
In the example, the first operator that we see is a timeout operator. A function that calls the broadcaster based on some time function, in this case it use the setTimeout
call to orchestrate how the broadcaster will be executed.
The first example shows us how the time definition affect the behavior of the broadcaster but more important it shows how we can use a closure inside the operator to change this behavior
let operator = (broadcaster) => (listener) => {
let currentValue = 0
broadcaster((value) => {
currentValue += value
setTimeout(() => {
// this call closes over currentValue
listener(currentValue)
}, currentValue * 1000)
})
}
In the example the line currentValue += value
is run immediately and by the definition of the broadcaster function, that line was call 3 times, then the value of currentValue
is "trap" inside the setTimeout
call as a closure.
π The key of an operator is to give it a good name to represent what is the actual behavior captured by the function definition
Solve Callback Hell with Composition
Callback hell is a concept used to describe a situation that happens when there are too many callbacks nested inside of each other, this results in "spagetti code" a piece of code that is hard to read, maintain and difficult to reason about. The composition patterns that we saw here can help to avoid this situation by creating flexibility with how the callbacks are managed.
The first step to solve this callback hell is to figured out what can be extracted as a function, in this example the is easy to see that there are at least 3 main function that can be created:
- one for the
fetch
callback
let getURL = () => {
fetch(`https://api.github.com/user/36073`)
.then((response) => response.json())
.then((data) => {
console.log(data)
})
}
- one for
setTimeout
call
let timeout = () => {
setTimeout(() => {}, 1000)
}
- and one for the click event
let click = () => {
document.addEventListener('click', (event) => {})
}
π At this step we just captured the behaviors described by the original code in different functions
The next step is to figured out how to wire up this functions.
First, we define the behavior that we need to capture, the relationship of nesting is essentially to invoke an outer function that accepts a callback that calls an inner function that also takes a callback, this behavior can be described by a function
let nest = (inner) => (outer) => (listener) => {
outer((value) => {
inner(listener)
})
}
So, by using the functions that we defined, this will look like:
nest(getURL)(nest(timeout)(click))((data) => {
console.log(data)
})
Is important to notice the pattern here, this code is hard to read and have multiple parenthesis that make it also hard to write and easy to mess, but this is a pattern that we already know and solve by composition in lesson 4
We can use pipe
from lodash/fp
package and refactor our code
let timeoutURL = pipe(
nest(timeout),
nest(getURL)
)
timeoutURL(click)((data) => {
console.log(data)
})
#Β Passing Values Through Callback Hell
In the previous talk we avoid the situation of passing values through the callbacks, to do this we need to define a mapping function that will accept the value and create the callback based on a configuration step that includes this value. To do that, each of the functions defined will now be wrapped by another function that accepts a config
parameter like:
let click = (config) => (listener) => {
document.addEventListener(config, listener)
}
With this in place we need to change our mental model and think about the mapInner
function as a function that creates or generates callbacks with the correct values.
let nest = (mapInner) => (outer) => (listener) => {
outer((config) => {
let innerr = mapInner(config)
inner(listener)
})
}
π So now, instead of thinking about nested callback, we can think of what value is the function receiving and how we want to configure the inner callback.
Top comments (0)