I'm quite sure the first term you'd heard of terms like Thunks
or Currying
in programming, your reaction would have been something similar to this,
Well, don't worry, I felt the same when I heard about these terms the first time especially from the programming aspect until I realized how useful they are!
Before we start, this blog post assumes that you have a basic knowledge of JavaScript and asynchronous programming using Promises and callbacks. Some knowledge about functional programming in JS would also be quite helpful to understand both these concepts better. Thunks along with Currying are quite useful concepts when it comes to Functional programming in JavaScript.
Let's dive deeper into these concepts and understand what they are, and how we can use them!
All about thunks
in JavaScript
We have been using callbacks in JavaScript almost everywhere for various use cases like asynchronous programming , listening to events , handling errors , and much more. Thunks basically extend the capabilities of callbacks and are quite useful when it comes to async programming in JavaScript, though they are still quite useful in certain scenarios involving synchronous programming.
What is a thunk
?
A thunk
is just a function that delays the computation of a value or some logic.
The word "thunk" is a programming term that means "a piece of code that does some delayed work"
Rather than executing some logic now, we can write a function body or code that can be used to perform the work later. Many of you working with React
probably know an awesome and plain simple library called redux-thunk
which as the name suggests is based on thunks, and is useful while doing state management using Redux
.
There are two perspectives in which you can use thunks
: synchronous , and asynchronous.
Synchronous way of using thunks
Let's talk about how you can use a synchronous thunk ,
const power = (base, exponent) => {
return Math.pow(base, exponent);
};
// `power` function is evaluated
// and the result is returned immediately
console.log(power(99, 9)); // 913517247483640800
// This is a thunk
const powerThunk = () => {
return power(99, 9);
};
// Returns the same result as calling the `power` function
console.log(powerThunk()); // 913517247483640800
In the above code snippet, power
is a simple function that calculates the power of a no and returns the calculated value immediately. powerThunk
is a synchronous thunk that delays this result until we call it. As you might have noticed, powerThunk
uses the power
function internally to calculate and return the calculated value.
The important part is that this thunk has become a wrapper around some particular state. In this case, it is a result of a potentially expensive operation. This pattern allows us to hide the internal details of the computation similar to the principle of Encapsulation of the Object Oriented Programming paradigm.
Now let's talk about how you can use thunks
from the async programming perspective.
Async thunks
So how can we describe an async thunk? It's just a simple function that doesn't need any additional parameters except for a callback function. In the case of async thunks, we can introduce a delay to process a synchronous function like the power
function from the previous example or use it to handle asynchronous API calls.
Let's have a look at both cases. We can modify the previous example for synchronous thunk to understand this better.
const powerAsync = (base, exponent, callback) => {
return setTimeout(() => callback(Math.pow(base, exponent)), 1000);
};
// This is an async thunk
const powerAsyncThunk = (callback) => {
return function () {
powerAsync(99, 9, callback);
};
};
// This async thunk now returns a function that
// can be called later on to calculate power.
const calculatePower = powerAsyncThunk((result) => console.log(result));
calculatePower();
In the above code snippet, we modified the power
function to powerAsync
function that fakes an asynchronous function using setTimeout
and takes an additional parameter callback
. Now, our async thunk powerAsyncThunk
delays the execution of powerAsync
function by returning a function. This makes it really useful as we can now just call this function returned by powerAsyncThunk
whenever we want in our code later on.
Now, let's talk about how we can deal with asynchronous API calls using async thunks.
const fetchCurrenciesData = (callback) => {
fetch("https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies.json")
.then(res => res.json())
.then(res => callback(res));
}
// This is an async thunk
const asyncThunk = (callback) => {
return function () {
fetchCurrenciesData(callback);
}
}
// This async thunk now returns a function that
// can be called later on to fetch data from the API.
const fetchCurrencies = asyncThunk((res) => console.log(res));
fetchCurrencies();
The fetchCurrenciesData
function makes an API call to fetch all the currencies of different countries and also takes a callback
function as a parameter. The async thunk asyncThunk
returns a function that can be called later on to fetch the data from the API whenever we want in our code.
The data after calling fetchCurrencies
function looks something like this,
{
aave: "Aave"
ada: "Cardano"
aed: "United Arab Emirates Dirham"
afn: "Afghan afghani"
algo: "Algorand"
doge: "Dogecoin"
dop: "Dominican peso"
dot: "Dotcoin"
dzd: "Algerian dinar"
egld: "Elrond"
egp: "Egyptian pound"
enj: "Enjin Coin"
eos: "EOS"
ern: "Eritrean nakfa"
etb: "Ethiopian birr"
ils: "Israeli New Shekel"
imp: "CoinIMP"
inj: "Injective"
inr: "Indian rupee"
iqd: "Iraqi dinar"
irr: "Iranian rial"
isk: "Icelandic krna"
jep: "Jersey Pound"
jmd: "Jamaican dollar"
jod: "Jordanian dinar"
jpy: "Japanese yen"
kava: "Kava"
kcs: "Kucoin"
kda: "Kadena"
kes: "Kenyan shilling"
...
}
Where exactly are thunks
used? 💡
Now that you have learned quite a lot about thunks, you might be wondering where exactly are thunks used.
Promises in JavaScript are based on the concept of thunks and under the hood it uses thunks. Have a look at this video which talks about how thunks can be used to sequence async function calls.
The redux-thunk middleware widely uses thunks internally. This enables us to use thunks with async logic inside, that can interact with a Redux store's
dispatch
andgetState
methods for React apps using Redux for state management.
All about Currying in JavaScript
Let's look at another quite useful concept within functional programming in JavaScript.
Currying is an advanced technique for working with functions. Its used not only in JavaScript but in other languages as well!
What is currying?
Currying is a transformation of functions that converts a function from callable as
f(a, b, c)
into callable asf(a)(b)(c)
.
We are transforming the function by breaking it down in such a way that it can be called in a step-by-step manner instead of calling it with all the arguments immediately. This technique is very useful, particularly when you don't have access to all the required parameters at an instant to be able to execute the logic inside your function.
Let's look at an example to understand this better. Suppose, we create a simple function multiplyTwoNos
that multiplies two nos and returns the calculated value.
const multiplyTwoNos = (no1, no2) => {
return no1 * no2;
};
console.log(multiplyTwoNos(3, 5)); // 15
// A curried function to multiple two nos
const curriedMultiplyTwoNos = function (no1) {
return function (no2) {
return no1 * no2;
};
};
console.log(curriedMultiplyTwoNos(3)(5)); // 15
In the above code snippet, curriedMultiplyTwoNos
is a function that uses currying to transform multiplyTwoNos(3, 5)
function call to a function call something like this - curriedMultiplyTwoNos(3)(5)
. The output is basically the same, but currying gives us more flexibility to calculate the multiplication of two numbers whenever we have access to both the numbers.
Advanced Currying Implementation
Now, let's have a look at a bit more advanced implementation of currying for multi-argument functions. We'll first create a generic currying function and then use the above example of multiplying some numbers and getting the calculated value.
// A function that takes a callback function `func`
// that defines the kind of operation/calculation that will be curried
const curry = function (func) {
return function curried (...args) {
// Ensure that we apply the operation defined by `func`
// with the max number of arguments supported by `func`.
if(args.length >= func.length) {
return func.apply(this, args);
} else {
// If number of parameters are insufficient to call `func` to
// perform the operation, recursively call `curried` with previously passed
// parameters as well as new parameters.
return function (...args2) {
return curried.apply(this, args.concat(args2));
}
}
}
}
There are a lot of things happening in the above code! 😅
But, don't get overwhelmed by seeing the above code, it's actually quite straightforward! Let's see how it works with an example and then we'll understand it properly.
const multiplyThreeNos = (no1, no2, no3) => {
return no1 * no2 * no3;
};
let curriedMultiplication = curry(multiplyThreeNos);
console.log(curriedMultiplication(4, 5, 6)); // 120, still callable normally
console.log(curriedMultiplication(4)(5, 6)); // 120, currying of 1st arg
console.log(curriedMultiplication(4)(5)(6)); // 120, full currying
The result of curry(func)
call is the wrapper curried
that looks like this:
function curried(...args) {
// Ensure that we apply the operation defined by `func`
// with the max number of arguments supported by `func`.
if (args.length >= func.length) {
return func.apply(this, args);
} else {
// If number of parameters are insufficient to call `func` to
// perform the operation, recursively call `curried` with previously passed
// parameters as well as new parameters.
return function (...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
In the above code, we are using some of the concepts like rest parameters, Recursions , and .apply().
There are basically two branches of code out of which one of them could get executed within the curried
function because of the if
statement.
If the number of arguments passed to
curried
is greater than or equal to the no of arguments defined forfunc
, then just pass the call tofunc.apply(this, args);
which will execute ignore the extra arguments passed if any.Else if the no of arguments passed to
curried
is less, then we recursively callcurried
with previously passed arguments as well as the new arguments by using the.concat()
method until the number of arguments forcurried
is not sufficient as per the definition offunc
.
Note : Currying requires the function to have a fixed number of arguments. A function that uses rest parameters, such as
f(...args)
, cant be curried this way as implemented above.
When and Why to use Currying? 💡
Currying can be useful for:
- Currying can be used when you want to create a function that will receive only single arguments or will receive arguments later on in your code.
- It can be used to trigger event listeners.
Let's look at some of the reasons why you may need to use Currying:
- Currying provides a way to make sure that your function receives all the arguments before you proceed with executing the core logic of your function.
- It divides your function into multiple smaller functions that can handle one responsibility. This makes your function pure and less prone to errors and side effects.
- It is used in the functional programming paradigm to create higher-order functions.
Conclusion
In this blog, we learned about Thunks & Currying , some of the most useful yet a bit advanced concepts of functional programming in JavaScript. We talked about thunks in detail like what are thunks , how can you use them, when to use them, and where are they used. Similarly, we learned about Currying , how to implement currying , advanced implementation of currying , and when to use it.
That's it from me folks, thank you so much for reading this blog! 🙌 I hope this blog was helpful and gave you an insight about Thunks & Currying and how you could use these very useful concepts of the functional programming paradigm in your JavaScript apps 😄
Top comments (0)