JavaScript queue functions and asynchronous execution details, javascript queue
Edit Note: I have seen similar queue functions in Review of others' JavaScript code, but I don't quite understand them. I used to ensure that the functions are called in order. After reading this article, I found that it can also be used for asynchronous execution.
Suppose you have several functions fn1, fn2, and fn3 that need to be called in order. The simplest method is:
fn1();fn2();fn3();
However, sometimes these functions are added one by one during runtime, and you do not know all functions when calling them. At this time, you can define an array in advance and push the function when adding the function, when needed, obtain them from the array in order and call them one by one:
Var stack = []; // execute other operations and define fn1stack. push (fn1); // execute other operations and define fn2 and fn3stack. push (fn2, fn3); // stack when calling. forEach (function (fn) {fn ()});
In this way, it doesn't matter whether the function has a name or not. You can directly pass the anonymous function in. To test:
Var stack = []; function fn1 () {console. log ('first call');} stack. push (fn1); function fn2 () {console. log ('second call');} stack. push (fn2, function () {console. log ('third call')}); stack. forEach (function (fn) {fn ()}); // output 'first call', 'second call', and 'third call' in sequence'
This implementation works normally so far, but we ignore the case that asynchronous function calls are involved. Asynchronization is an unavoidable topic in JavaScript. It is not intended to explore various terms and concepts related to Asynchronization in JavaScript. Please refer to them by yourself (for example, a famous commentary ). If you know that the following code will output 1, 3, and 2, continue to look at it:
console.log(1);setTimeout(function() { console.log(2);}, 0);console.log(3);
If a function in the stack queue is similar to an asynchronous function, our implementation will be messy:
Var stack = []; function fn1 () {console. log ('first call')}; stack. push (fn1); function fn2 () {setTimeout (function fn2Timeout () {console. log ('second call') ;}, 0) ;} stack. push (fn2, function () {console. log ('third call')}); stack. forEach (function (fn) {fn ()}); // output 'first call', 'third call', and 'second call'
Obviously, fn2 is called in sequence, but the function fn2Timeout () in setTimeout {console. log ('second call')} is not executed immediately (even if the timeout is set to 0); fn2 returns immediately after the call, and then fn3 is executed, fn3 is the final turn of fn2Timeout after the worker is executed.
How can this problem be solved? In our analysis, the key here is fn2Timeout. We must wait until it is actually executed to call fn3. Ideally, it is like this:
function fn2() { setTimeout(function() { fn2Timeout(); fn3(); }, 0);}
But this is equivalent to replacing the original fn2Timeout with a new function, and then inserting the original fn2Timeout and fn3. This kind of dynamic syntax for getting rid of the original function is called Monkey Patch. According to the mantra of our programmers, "you can do what you do." But it is a bit difficult to write, and it is easy to wrap yourself in. Is there any better way?
Let's take a step back and do not force fn3 to be executed until fn2Timeout is fully executed. Instead, it is called in the last line of the fn2Timeout function body:
Function fn2 () {setTimeout (function fn2Timeout () {console. log ('second call'); fn3 (); // note {1 }}, 0 );}
This looks better, But fn3 is not available when fn2 is defined. Where does fn3 come from?
There is another problem. Since fn3 needs to be called in fn2, we cannot call fn3 through stack. forEach. Otherwise, fn3 will be called twice.
We cannot write fn3 into fn2. Instead, we only need to find the next function of fn2 in the stack at the end of fn2Timeout, and then call:
Function fn2 () {setTimeout (function fn2Timeout () {console. log ('second call'); next () ;}, 0 );}
This next function is used to locate and execute the next function in the stack. Next:
Var index = 0; function next () {var fn = stack [index]; index = index + 1; // In fact, you can also use shift to take fn out if (typeof fn = 'function') fn ();}
Next uses the stack [index] to obtain the function in the stack. Each call to next adds 1 to the index to retrieve the next function.
Next:
Var stack = []; // define index and nextfunction fn1 () {console. log ('first call'); next (); // each function in the stack must call 'Next'}; stack. push (fn1); function fn2 () {setTimeout (function fn2Timeout () {console. log ('second call'); next (); // call 'Next'}, 0);} stack. push (fn2, function () {console. log ('third call'); next (); // The Last One may not be called, and the call is useless .}); Next (); // call next, and output 'first call', 'second call', and 'third call' in sequence '.
Now stack. the forEach row has been deleted. We call next one by ourselves. next will find the first function fn1 in the stack and call next in fn1 to find and execute the next function fn2, in fn2, call next, and so on.
Each function must call next. If a function is not written, the program ends directly after the function is executed without any mechanism.
After learning about the implementation of function queue, you should be able to solve the following questions:
// Implement a LazyMan, which can be called in the following way: LazyMan ("Hank")/* output: Hi! This is thank you! */LazyMan ("Hank"). sleep (10). eat ("dinner") output/* output: Hi! This is thank you! // Wait for 10 seconds .. Wake up after 10Eat dinner ~ */LazyMan ("Hank"). eat ("dinner"). eat ("supper")/* output: Hi This is Hank! Eat dinner ~ Eat supper ~ */LazyMan ("Hank"). sleepFirst (5). eat ("supper")/* Wait 5 seconds and output Wake up after 5Hi This is thank you! Eat supper * // and so on.
In Node. js, the well-known connect framework achieves the middleware queue in this way. If you are interested, you can check its source code or this article explains what is connect middleware.
You may see that this next can only be placed at the end of the function at the moment. If it is placed in the middle, the original problem will occur:
Function fn () {console. log (1); next (); console. log (2); // next () If an asynchronous function is called, console. log (2) will be executed first}
Redux and koa use different implementations to place next in the middle of the function. After the function is executed, it is very clever to fold back and execute the code below next. Write again when you are free.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.