How does thunk solve callback hell with JS?

This is the 19th day of my participation in the First Challenge 2022, for more details: First Challenge 2022 “.

Introduction:

Multiple asynchronous nested calls tend to generate many callbacks, resulting in many nested callback functions, which creates callback hell. Callback hell is very difficult to read in development, and the thunk function is one way to avoid it in order to keep code readable and elegant. The thunk function may not be used very often in everyday development because it is a high level use of JS functions, but you will probably encounter it in interviews or in good source libraries, and it will be difficult to read the source code without understanding what the thunk function is. So we’ll talk about what callback hell is, what the Thunk function is, and how to use the Thunk function to solve callback hell.

The callback hell

First look at the code:

const fn = function (str, callback) { setTimeout(() => { console.log(str) if (callback) { callback() } }, 100); } fn (' 1 ', () = > {fn (' 2 ', () = > {fn (' 3 ', () = > {the console. The log (' 'task has been completed)})})})Copy the code

Print result:

1 2 3 The task is completeCopy the code

There is the function fn, followed by the revenue argument STR and the callback function callback. The fn delay prints STR and then invokes the callback function.

A very simple function with a scenario where you need to call it three times, each time after the result is returned. Then, as you can see in the code above, fn is nested in multiple layers, which makes the code very unreadable and looks bloated, functional but not elegant enough. This is what we call callback hell.

The same is likely to happen in real development:

If there are three interfaces and the interfaces must be called sequentially, then you have to wait for the first interface to return the result and then call the second interface again, and wait for the second interface to return the result and then call the third interface, the above callback hell situation is likely to occur.

Of course we can use async/await and so on, but the focus of this discussion is to use thunk functions.

Thunk function

What is the Thunk function? The compiler’s “call by name” implementation usually puts arguments in a temporary function that is passed into the function body. This temporary function is called the Thunk function.

Thunk, as a temporary function, is a temporary function that has a purpose. The purpose of the thunk function is to separate arguments from callbacks.

1. Write the simplest thunk function

Look at the following code:

const fn = function (str, callback) {
  setTimeout(() => {
    console.log(str)
    if (callback) {
      callback()
    }
  }, 100);
}

const thunk = (str) => {
  return (callback) => {
    return fn(str, callback)
  }
}
Copy the code

There is a function called thunk, which receives a string of inputs that returns a function b with callback, which executes fn and returns the result.

So we can analyze:

  1. The thunk function’s input argument becomes a string, and there will be no callback.

  2. The thunk function returns a function with a callback as an input parameter.

  3. The returned function will actually execute fn

Try calling:

const thunk1 = thunk('1')
const thunk2 = thunk('2')
const thunk3 = thunk('3')

cconsole.log(thunk1) // [Function (anonymous)]
Copy the code

Here thunk1, thunk2, thunk3 are still functions.

Call method:

const thunk1 = thunk('1')
const thunk2 = thunk('2')
const thunk3 = thunk('3')
thunk1(() => { thunk2(() => { thunk3() }) }) // 1 2 3
Copy the code

Although the result is the same as the initial result, this seems to be the same as the beginning of the hell, and does not reflect the advantages of the Thunk function.

Continue to optimize:

2. Coriization function

The thunk function is called a partial application function, the STR argument is fixed, and returns a function that receives the remaining argument callback.

// const thunk = (str) => { // return (callback) => { // return fn(str, Callback) //} //} const thunk => callback => fn(STR, callback) // thunkCopy the code

The essence is the same.

3. Wrap the thunk result in an array

const thunk = str => callback => fn(str, Callback) const thunk1 = thunk('1') const thunk2 = thunk('2') const thunk3 = thunk('3') // thunk1(() => {  thunk2(() => { thunk3() }) }) const arr = [thunk1, thunk2, thunk3] const generate = (arr) => { arr[0](() => arr[1](() => arr[2]())) // 1 2 3 } generate(arr)Copy the code

We wrap the thunk result functions in an array, and then still nest the calls, essentially the same code as before, but just straight from the array. Why would you put it in an array? The reason is that it’s easy to create, so we don’t need to create a thunk and put it in an array. We just need to focus on how to generate.

So far, there is no essential difference from the previous one, still nested calls. But we’ve set the stage for optimization.

4. ReduceRight Overlay

In generate, we can superimpose elements in arR.

const generate = (arr) => {
  arr.reduceRight((a, b) => () => {
    return b(() => { a() })
  })()
}
generate(arr) // 1 2 3
Copy the code

5. Complete code

const fn = function (str, callback) { setTimeout(() => { console.log(str) if (callback) { callback() } }, 100); } const thunk = str => callback => fn(str, Const thunk1 = thunk('1') const thunk2 = thunk('2') const thunk3 = thunk('3') const thunk1 = thunk('1') const thunk2 = thunk('2') const thunk3 = thunk('3') const Arr = [thunk1, thunk2, thunk3] // Generate = (arr) => {arr.reduceright ((a, b) => () => { return b(() => { a() }) })() } generate(arr) // 1 2 3Copy the code

And you’re done solving callback hell through Thunk!

In real development, we could use the Generate method, along with a createTrunk method, as a method in the utility class, and just use it out of the box when we hit callback hell.

So now you have another util method in your private toolkit? !

6. Multiple arguments + callback

In this case, there is only one parameter. Can multiple parameters be used here?

const fn = function (geeting, yourName, callback) { setTimeout(() => { console.log(geeting + ',' + yourName) if (callback) { callback() } }, 100); } const thunk = (greeting, yourName) => callback => fn(greeting, yourName, Callback) const thunk1 = thunk('hello', '1') const thunk2 = thunk('hi', '1') '2') const thunk3 = thunk('ok', '3') const arr = [thunk1, thunk2, Thunk3] const generate = (arr) => {arr.reduceright (a, b) => () => { return b(() => { a() }) })() } generate(arr) // hello,1 // hi,2 // ok,3Copy the code

The answer is yes.