Async JS Patterns using Thunks

sebasqui

SebasQuiroga

Posted on February 1, 2022

Async JS Patterns using Thunks

This is the second part of the series Async JS Patterns, in the previous post I wrote about callbacks and how they help us to sort some common programming puzzles such as concurrency. In this post we will talk about a slightly different approach of callbacks, thunks.

Let’s first revise an important concept behind thunks.

A closure is the combination of a function enclosed with references to its surrounding state (the lexical environment). MDN

Let’s check the example provided by MDN, (I intentionally changed some comments and lines for a better understanding).

function makeFunc() {
    var name = 'Mozilla';
    function displayName() {
        console.info(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();   
// it executes the function displayName() 
// using all the private state (in this case the string) 
// from its parent function makeFunc()
Enter fullscreen mode Exit fullscreen mode

As we can see in the previous snippet, a closure lets a function remembers the collection of states (similar to a conventional memory system) and play with them, it is very simple but applied in async tasks makes it very interesting.

Practical speaking a thunk is the returned function in a function, like in the next code:

function helloWorld() {
// Some private state
    return function thunk(cb){
                // We have access to all private state from helloWorld() - closure -
                // And aditionally we can use it as a mechanism for delaying execution
                // (check the comments at the end of this example)
                cb()
    }
}

let th = helloWorld()
th(cb) 
// Whenever the function helloWorld() has finished its instructions,
// we pass to the return function a callback with more instructions,
// resulting in a sequential flow!
Enter fullscreen mode Exit fullscreen mode

The previous example is a taste of async thunks but let’s keep it simple, we will dig into async thunks in a while.

Technically speaking a thunk is a container around a collection of state, it is a function that returns an other function thus it has access to the parent private state (closure).

Let’s consider the next synchronous thunk:

function sumThunk(a, b) {
    return a+b
}

function commonFunction(){
    const a = 10 
    const b = 10  
    return sumThunk(a,b) //This returns a thunk function that make use of the parent private state, 
                         // everytime it is called, it will always remember this state.
                         // It acts like a container around a value(s), pretty similar to a token
}

const total = commonFunction()
console.info(total) // 20
Enter fullscreen mode Exit fullscreen mode

Asynchronous thunks behave pretty similar, however we pass as parameter a function(callback) so whenever the value is ready (like the fetching of some data) it executes the function (resulting as a sequentially and blocking way of sorting asynchronous tasks).

Let’s code a program that fetches some data, but this time we will code it as intuitive as our brain would most likely sort it.

function fetchAPI(rq, time) {
    const apiResponses = {
        'file1': 'First File',
        'file2': 'Second file'
    }

    setTimeout(function () {
        console.info(apiResponses[rq])
    }, time)
}

function executer(rq, time) {
        fetchAPI(rq, time) 
}

let th1 = executer('file1' , 3000)
let th2 = executer('file2' , 100)

// Second file
// First file
Enter fullscreen mode Exit fullscreen mode

The output of the previous example is not what we initially expected, we want the file1 to be fetched first and then the file2. The real problem here is with JS engine, because it is responsible for executing instructions as fast as possible without worrying about waiting for slow responses, so what happens here is that as the file1 needs 3 seconds to execute and the file2 just 100 seconds, the JS engine will choose the file2 execution first than the file1, contrary to what we really want.

Now let’s consider the next example using thunks for managing async tasks correctly (sequentially and blocking)

function fetchAPI(rq, time, cb) {
    const apiResponses = {
        'file1': 'First File',
        'file2': 'Second file'
    }

    setTimeout(function () {
        cb(apiResponses[rq]) // - function ready() - lazy executed!
    }, time)
}

function executer(rq, time) {
    // we return a thunk with the - function ready() - as a 'future' callback
    return function thunk(cb) {
        fetchAPI(rq, time, cb) 
    }
}

// We return the function of  - function executer() - in the variable th, 
// as it is a function we can pass a param(in this case a callback - function ready() ), 
let th1 = executer('file1' , 3000)
let th2 = executer('file2' , 100)

th1(function ready(salida){
    console.info(salida) 
    // When JS engine comes in this line the first task has completed, 
    // so now we launch the second one
    th2(function ready(salida) {
        console.info(salida)
    })
})
Enter fullscreen mode Exit fullscreen mode

References

💖 💪 🙅 🚩
sebasqui
SebasQuiroga

Posted on February 1, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related