18

in the Boilerplate I am using for a React Redux project I came across this comment in the code:

This is a thunk, meaning it is a function that immediately returns a function for lazy evaluation. It is incredibly useful for creating async actions, especially when combined with redux-thunk!

Now, if I understand this correctly, lazy evaluation is the process of returning a function. What is the purpose of returning a function though, and how is this great for creating async actions?

Oh also, is a thunk just a function?

Flip
  • 6,233
  • 7
  • 46
  • 75
Michael Jones
  • 2,222
  • 18
  • 32

2 Answers2

34

A thunk is a function that takes no arguments and returns something (or does something as a side effect). Lazy evaluation is the process of deferring the evaluation of an expression until later, and this can be done with thunks:

// Not lazy
var value = 1 + 1  // immediately evaluates to 2

// Lazy
var lazyValue = () => 1 + 1  // Evaluates to 2 when lazyValue is *invoked*

You can also make return values lazy:

// Not lazy
var add = (x, y) => x + y
var result = add(1, 2)  // Immediately evaluates to 3

// Lazy
var addLazy = (x, y) => () => x + y;
var result = addLazy(1, 2)  // Returns a thunk which *when evaluated* results in 3.

Finally we can defer some asynchronous action:

// Not lazy
var callApi = spec => fetch(spec.url, spec.options);
// Immediately returns a Promise which will be fulfilled when the network response is processed.
var result = callApi({url: '/api', options: {}});

// Lazy
var callApiLazy = spec => () => fetch(spec.url, spec.options);
var result = callApiLazy({url: '/api', options: {}});
// result is a thunk that when evaluated will return a Promise ...
// which will be fulfilled when the network response is processed.

Now a thunk does not have to take zero arguments - you could return a lazy value that requires more arguments to successfully evaluate. This is properly known as "currying":

// Curried add (not lazy)
var add = x => y => x + y
var add3 = add(3)
var result = add3(7)  // Immediately evaluates to 10

redux-thunk lets you return functions, rather than objects, as actions and invokes your function with a dispatch function. You can then lazily produce an action (or actions) either synchronously or asynchronously. Most of the time, you would want to use this to allow you to dispatch asynchronously.

See also:

Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
  • 2
    _"takes no arguments"_ is not true in this instance... a redux thunk accepts `dispatch`, which allows for an action dispatch to be deferred. – sdgluck Aug 11 '16 at 19:50
  • Yes, `redux-thunk` is stretching the term thunk a little bit. I'll add that in. – Sean Vieira Aug 11 '16 at 19:54
  • Hello, this is very interesting! So basically in the first example, `value` is not lazy because its a variable, and it is immediately evaluated. Where as `lazyValue` is a function of a variable, and therefor not evaluated until the variable is called. In the second example, would the `not lazy` because considered so because `result` is calling `add`s function? Also, for the `lazy` in example 2, is that a typo on `result`? Shouldn't it be `addLazy(1,2)`? – Michael Jones Aug 11 '16 at 19:58
  • Yes it is a typo @MichaelJones - thank you! As for the rest, `lazyValue` is a variable that holds a function that when called will produce the value we want, where `value` holds the value we want. – Sean Vieira Aug 11 '16 at 20:03
  • Ok, so in the second group of `lazy` and `not lazy` is where I am getting confused. For the `not lazy`, `add` is holding a function, wouldn't that make it `lazy` because it isn't evaluated until `result` calls `add(1,2)`. As for the `lazy` part of the example, `addLazy` has a function that is returning a function correct? So basically, does that mean that the `result` doesn't even evaluate `addLazy`? `addLazy` is only evaluated when the `result` variable is called right? I think I am starting to get the hang of this, I hope anyhow! – Michael Jones Aug 11 '16 at 20:14
  • Also, sorry for the secondary comment, but is the purpose of `lazy evaluation` so it loads faster at run-time, and it only loads the time-consuming code when necessary? – Michael Jones Aug 11 '16 at 20:32
  • Example #2 is an example of how to defer work at the `return` point of a function - `add` does its work and then returns, `addLazy` returns *a way to do the work at the proper time*. As for the purpose of thunks, it's to let the consumer determine when / whether to do the work - which *can* have a performance boosting affect, but not necessarily. – Sean Vieira Aug 11 '16 at 21:32
1

Normally, Redux action creators are synchronous, meaning that, when you call them, you would expect them to return an Action and the Reducers to be called immediately and the state to change on the fly. You also expect this process to be really fast, because nothing but a small CPU-bound operation would be performed.

However, sometimes you want your action creator to go to the server, or do some non-CPU bound operation that would take a while. That's when it makes sense to return a function instead.

When your action creator returns a function, it returns immediately. From the perspective of who calls the action creator, nothing strange happened. Everything is as usual. But internally, instead of returning the Action object, your action creator returned a function like this..

function DoSomethingAsync() {
    return (dispatch) => {
        // go do something realllly slowly.
        // ok now get the RESULT and call a regular action
        dispatch(mySyncAction(RESULT));
    }
}

Normally DoSomethingAsync would return an Object. What the Redux-Thunk middleware does is to detect that a function was returned instead.. So, it does nothing but to call this function passing the same dispatch as usual.

Now it's responsability of the callback to call dispatch to dispatch a SYNC action.

Andre Pena
  • 56,650
  • 48
  • 196
  • 243