Best practices of using the Thunk in JavaScript

By | December 24, 2019

The evaluation strategy of a compiler is usually divided into pass-by-call and pass-by-call. Thunk functions are implemented by pass-by-call by the compiler. They often put parameters into a temporary function and then put This temporary function is passed into the function body. This temporary function is called the Thunk function.

Evaluation strategy

Compiler evaluation strategies are usually divided into call-by-value and call-by-name calls. In the following example, an expression is passed as a parameter, and the method of implementation by call-by-name and call-by-name is different.

var x = 1;
function s(y){
    console.log(y + 1); // 3
}
s(x + 1);

In the above example, the result of the execution is the same, whether it is called by value or by name, but the calling process is different:

  • Call by value: first, calculate x + 1, and then pass the calculation result 2 to the s function, which is equivalent to callings(2).
  • Call by name: pass the x + 1 expression directly to y, and then calculate x + 1 when using it, which is equivalent to calculating (x + 1) + 1.

Value-calling and name-calling have their pros and cons. Value-calling is relatively simple, but when evaluating a parameter, this parameter has not been used, which may cause unnecessary calculations. Call by name can solve this problem, but the implementation is relatively complicated.

var x = 1;
function s(y){
    console.log(y + 1); // 3
}
s(x + 1, x + 2);

In the above example, the function s does not use the value obtained by the expression x + 2. If it is called by name, the expression is only passed in and not calculated, as long as x + 2 is not used in the function. The expression will not be calculated. If it is called by value, the value of x + 2 will be calculated and then passed in. If this value is not used, then there is one more unnecessary calculation. The Thunk function is built as an implementation of passing by name. It is often to put parameters into a temporary function and then pass this temporary function into the function body. This temporary function is called the Thunk function.

var x = 1;

function s(y){
    console.log(y + 1); // 3
}

s(x + 1);

var x = 1;

function s(thunk){
    console.log(thunk() + 1); // 3
}

var thunk = function(){
    return x + 1;
}

s(thunk);

Thunk in JavaScript

The evaluation strategy in JavaScrpt is to call by value. The use of Thunk functions in Js needs to be implemented manually and has different meanings. In JavaScript, Thunk functions do not replace expressions, but multi-parameter functions, replacing them with Single parameter version, and only accepts the callback function as a parameter.

var delayAsync = function(time, callback, ...args){
    setTimeout(() => callback(...args), time);
}

var callback = function(x, y, z){
    console.log(x, y, z);
}

delayAsync(1000, callback, 1, 2, 3);

var thunk = function(time, ...args){
    return function(callback){
        setTimeout(() => callback(...args), time);
    }
}

var callback = function(x, y, z){
    console.log(x, y, z);
}

var delayAsyncThunk = thunk(1000, 1, 2, 3);
delayAsyncThunk(callback);

Implement a simple Thunk function converter. For any function, as long as the parameter has a callback function, it can be written as a Thunk function.

var convertToThunk = function(funct){
  return function (...args){
    return function (callback){
      return funct.apply(this, args);
    }
  };
};

var callback = function(x, y, z){
    console.log(x, y, z);
}

var delayAsyncThunk = convertToThunk(function(time, ...args){
    setTimeout(() => callback(...args), time);
});

thunkFunct = delayAsyncThunk(1000, 1, 2, 3);
thunkFunct(callback);

Thunk functions may be used less before ES6, but after ES6, Generator functions have appeared, and they can be used for automatic process management of Generator functions by using Thunk functions. The first is about the primary use of the generator function. Calling a generator function does not immediately execute the statements in it, but returns an iterator object of the generator, which is a pointer to the internal state object. When the iterator’s next() method is called for the first time (follow-up), the statement within it will be executed until the first (follow-up) yield occurs, and the yield is followed by the value to be returned by the iterator, that is, the pointer It will execute from the function head or the place where it last stopped to the next yield. If the yield is used, it means that the execution power is transferred to another generator function (the current generator is suspended).

function* f(x) {
    yield x + 10;
    yield x + 20;
    return x + 30;
}
var g = f(1);
console.log(g); // f {<suspended>}
console.log(g.next()); // {value: 11, done: false}
console.log(g.next()); // {value: 21, done: false}
console.log(g.next()); // {value: 31, done: true}
console.log(g.next()); // {value: undefined, done: true}

Because the Generator function can temporarily suspend the execution of the function, he can completely operate an asynchronous task. When the previous task is completed, the next task is continued. The following example is to express an asynchronous task synchronously. The next timer task will not be performed until the timer is completed. You can solve an asynchronous nesting problem in this way. For example, the callback method needs to add a callback after a network request for the next request, which can easily cause a callback. Hell, and this problem can be solved by the Generator function. Async/await is an asynchronous solution implemented by the Generator function and Promise.

var it = null;

function f(){
    var rand = Math.random() * 2;
    setTimeout(function(){
        if(it) it.next(rand);
    },1000)
}

function* g(){ 
    var r1 = yield f();
    console.log(r1);
    var r2 = yield f();
    console.log(r2);
    var r3 = yield f();
    console.log(r3);
}

it = g();
it.next();

Although the above example can be automatically executed, it is not convenient enough. Now we implement automatic process management of the Thunk function, which automatically helps us handle the callback function. We only need to pass some parameters required for the function execution in the Thunk function, such as the example Index. You can write the function body of the Generator function, receive the parameters of the function execution in the Thunk function through the variable on the left when using the Thunk function for automatic process management. You must ensure that the yield is a Thunk function.
Regarding the automatic process management run function, you first need to know that if you call the next() method, if the parameter is passed, then this parameter will be passed to the variable on the left side of the last executed yield statement. In this function, the next execution is performed for the first time. No parameters are passed at the time, and there is no statement to receive variables on the first yield. There is no need to pass parameters. The next step is to determine whether the generator function has been executed. If it has not been executed here, then the custom. The following function is passed into res.value.
It should be noted here that res.value is a function. You can execute the commented line in the example below, and then you can see that this value is f(funct){…}, At this time, after passing the custom next function, we will hand over the execution permission of next to the function f. After this function executes the asynchronous task, the callback function will be executed. In this callback function, the generator will be triggered. The next method and the following method passes parameters. As mentioned above, after passing in the parameters, it will be passed to the variable on the left side of the last executed yield statement. Then in this execution, the parameter value will be passed to r1 and then continue to execute next, continue to reciprocate until the generator function finishes running, so that the automatic management of the process is realized.

function thunkFunct(index){
    return function f(funct){
        var rand = Math.random() * 2;
        setTimeout(() => funct({rand:rand, index: index}), 1000)
    }
}

function* g(){ 
    var r1 = yield thunkFunct(1);
    console.log(r1.index, r1.rand);
    var r2 = yield thunkFunct(2);
    console.log(r2.index, r2.rand);
    var r3 = yield thunkFunct(3);
    console.log(r3.index, r3.rand);
}

function run(generator){
    var g = generator();
    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        // console.log(res.value);
        res.value(next);
    }
    next();
}
run(g);