Detailed description of the fiber library in nodejs
This article mainly introduces the fiber (fiber path) Library in nodejs. This article describes the installation, API introduction, and method usage examples of node-fiber ers. For more information, see
Fiber/Optical fiber
In the operating system, in addition to processes and threads, there is also a fiber (also called coroutine) with less applications ). Fiber threads are often used for comparison with threads. For the operating system, they are all lightweight running states. It is generally considered that the fiber process is more lightweight than the thread, and the overhead is smaller. The difference is that a fiber is created by a thread or fiber, and the fiber scheduling is completely controlled by the user code. It is a non-preemptible Scheduling Method for the system kernel, the fiber program implements cooperative multitasking, while the threads and processes are scheduled by the kernel and implement preemptible multitasking according to the priority. In addition, the system kernel does not know the specific running status of the fiber. The usage of the fiber is actually unrelated to the operating system.
In node, a single thread is only for javascript, and its underlying layer is full of multithreading. To implement multithreading in javascript, a common practice is to write C ++ addon to bypass the javascript single-thread mechanism. However, this method increases the difficulty and cost of development and debugging. Like many other scripting languages, we can also introduce the fiber path concept to node.
Node-fibers
Node-fibers provides the fiber path function for node. I have not tested the ideal results for multithreading, but it plays a significant role in asynchronous synchronization. It may be useful in reducing node call stacks and infinite recursion. This document describes how to use the node-fabers library and how to use it asynchronously.
Install
Node-fibers is written in C language. to directly download the source code, you need to compile it. Generally, you can directly install npm:
The Code is as follows:
Npm install fibers
Use of the ERS ers Library
API
1. Fiber (fn)/new Fiber (fn ):
Create a fiber program, which can be used as a constructor or a common function call. For example:
The Code is as follows:
Function fibo (n ){
Return n> 1? Fiber (n-1) + fiber (n-2): 1;
}
Fiber (function (){
Console. log (fibo (40 ));
});
When run () is called, the fiber starts and a new stack is assigned to fn. fn runs on the new stack until fn returns a value or calls yield (). After fn is returned or yield () is called, the stack is reset. When run () is called again, the fiber starts again and fn runs in the first allocated stack.
2. Fiber. current:
Obtain the current optical fiber and perform operations on it. If you specify a variable associated with it, make sure that the fiber can be released. Otherwise, V8's garbage collection mechanism will ignore this part of the memory and cause memory leakage.
3. Fiber. yield (param ):
This function has been mentioned in the previous description. The yield () method is used to interrupt the fiber process, which is similar to return to a certain extent. Once yield () is executed, subsequent code in the Fiber will not be executed. For example:
The Code is as follows:
Var fiber = Fiber (function (){
Console. log ("Fiber Start ");
Fiber. yield ();
Console. log ("Fiber Stop ");
}). Run ();
// Output: "Fiber Start"
After the command is executed, only "Fiber Start" is output, and the last output command is not executed. If you pass in a parameter to yield (), this parameter is used as the return value of run.
The Code is as follows:
Var fiber = Fiber (function (){
Fiber. yield ("success ");
}). Run ();
Console. log (fiber); //-> "success"
4. Fiber. prototype. run (param ):
This method is already very familiar. Previously, we vaguely mentioned two tenses for calling run (). One is that when Fiber is not started, Fiber is yield at the moment. In these two tenses, the run () action is not the same.
When Fiber is not started, run () accepts a parameter and passes it to fn as its parameter. When Fiber processes the yielding state, run () accepts a parameter and uses it as the return value of yield (). fn does not run from the beginning, but runs from the break. The following is a small example of the relationship between fn, yield, and run parameters and return values:
The Code is as follows:
Var Fiber = require ('scheduler ');
Var fiber = Fiber (function (){
Console. log ("first call run :");
Console. log ("fn parameter:" + );
Var B = Fiber. yield ("yield ");
Console. log ("second call run :");
Console. log ("fn parameter:" + );
Console. log ("yield returned value:" + B );
Return "return ";
});
// Run () for the first time ()
Var c = fiber. run ("One ");
// Run () for the second time ()
Var d = fiber. run ("Two ");
Console. log ("Call yield, run returns:" + c );
Console. log ("fn finished, run returns:" + d );
The output is as follows:
The Code is as follows:
/*
The first call to run:
Fn parameter: One
The second call to run:
Fn parameter: One
Yield return value: Two
Call yield and run to return: yield
After fn is run, run returns: return
*/
From the above example, we can see that yield's usage is quite different from the current javascript syntax. In other languages (C #, Python, etc.), the yield keyword has been implemented as an iterator interrupt. You may wish to implement an iterator on the node to learn more about the use of yield. Take the Fibonacci sequence that begins with the following as an example:
The Code is as follows:
Var fiboGenerator = function (){
Var a = 0, B = 0;
While (true ){
If (a = 0 ){
A = 1;
Fiber. yield ();
} Else {
B + =;
B =? A = 1: a = B-;
Fiber. yield (B );
}
}
}
Var f = new Fiber (fiboGenerator );
F. next = f. run;
For (var I = 0; I <10; I ++ ){
Console. log (f. next ());
}
Output:
The Code is as follows:
/*
1
1
2
3
5
8
13
21
34
55
*/
There are two issues to be aware of. First, yield is a method, more like a keyword. Unlike run, yield does not need to rely on a Fiber instance, but run does. If you call run in Fiber, you must use: Fiber. current. run (); second, yield itself is a reserved keyword for javascript and is not sure whether or not it will be enabled, so the code may face changes in the future.
5. Fiber. prototype. reset ():
We already know that Fiber may have different tenses and may affect run behavior. The reset method restores to the initial state no matter what status the Fiber processes. Then execute run to re-run fn.
6. Fiber. prototype. throwInto (Exception ):
In essence, throwInto throws an exception sent to it and uses the exception information as the return value of run. If the Fiber does not handle the exception thrown by the Fiber, the exception will continue to bubble up. Whether or not the exception is handled, it forces yield to interrupt the Fiber.
Use of the future Library
It is not always reasonable to directly use Fiber in node, because the Fiber API is simple and will inevitably produce repeated and lengthy code in actual use, which is not conducive to maintenance. We recommend that you add an abstraction layer between node and Fiber so that Fiber can work better. The future library provides such an abstraction. The future library or any abstraction layer may not be perfect. No one is right or wrong, but it is not applicable. For example, the future database provides us with simple APIs for asynchronous synchronization, but it is powerless to encapsulate generator (similar to the above Fibonacci series generator.
The future library does not need to be downloaded and installed separately. It is already included in the fibers library. You only need to use var future = require ('ers ERS/ure.
API
1. Function. prototype. future ():
Add the future Method to the Function type and convert the function into a "funture-function ".
The Code is as follows:
Var futureFun = function power (){
Return a *;
}. Future ();
Console. log (futureFun (10). wait ());
In fact, the power method is executed within the fiber. However, there is a bug in the current version of future, and there is no specific official description. If you need to use this function, delete rows 339th and 350th of future. js.
2. new Future ()
The constructor of the Future object is described in detail below.
3. Future. wrap (fn, idx)
The wrap method encapsulates asynchronous to synchronous operations, which is the most valuable method in the future library. Fn indicates the function to be converted, idx indicates the number of parameters accepted by fn, And the callback method is considered as the last parameter (the API is controversial, and some people tend to pass the callback where it should be, fortunately, the wrap method is relatively simple, and the code can be easily modified ). Let's look at an example to understand the usage of wrap:
The Code is as follows:
Var readFileSync = Future. wrap (require ("fs"). readFile );
Fiber (function (){
Var html = readFileSync ("./1.txt"). wait (). toString ();
Console. log (html );
}). Run ();
From this example, we can see that Fiber asynchronous synchronization is indeed very effective. Apart from the syntax of one more step. wait (), other fs. readFileSync methods already provided by fs have nothing to do with it.
4. Future. wait (futures ):
This method has been seen many times before. As the name suggests, it is used to wait for results. If you want to wait for the results of a future instance, you can directly call futureInstance. wait (). If you want to wait for the results of a series of future instances, call Future. wait (futuresArray ). Note that, in the second usage, a future instance encounters an error during running and the wait method does not throw an error. However, you can use the get () method to directly obtain the running result.
5. Future. prototype. get ():
The get () method is similar to the first method of wait (). The difference is that get () immediately returns the result. If the data is not ready, get () throws an error.
6. Future. prototype. resolve (param1, param2 ):
The above wrap method always provides a future that is actually consuming the callback function of the Asynchronous Method and directly returning the asynchronous result. In fact, future also provides a solution for setting callback functions through the resolve method. Resolve can accept up to two parameters. If only one parameter is input, future considers that a node-style callback function is passed. For example:
The Code is as follows:
FutureInstance. resolve (function (err, data ){
If (err ){
Throw err;
} Else {
Console. log (data. toString ());
}
});
If two parameters are input, the error and data are processed separately. The example is as follows:
The Code is as follows:
FutureInstance. resolve (function (err ){
Throw err;
}, Function (data ){
Console. log (data. toString ());
});
In addition, future does not differentiate the call time of resolve. If the data is not ready, the callback function is pushed into the queue and is uniformly scheduled by the resolver () method. Otherwise, the callback function is executed immediately when the data is retrieved.
7. Future. prototype. isResolved ():
Returns a Boolean value indicating whether the operation has been executed.
8. Future. prototype. proxy (futureInstance ):
The proxy method provides a proxy for the future instance, which is essentially a packaging of the resolve method. In fact, the callback method of one instance is used as the callback executor of another instance. For example:
The Code is as follows:
Var target = new Future;
Target. resolve (function (err, data ){
Console. log (data)
});
Var proxyFun = function (num, cb ){
Cb (null, num * num );
};
Fiber (function (){
Var proxy = Future. wrap (proxyFun) (10 );
Proxy. proxy (target );
}). Run (); // output 100
Although the proxy is executed, the final target callback function is executed and the target callback function is driven based on the proxy execution result. This kind of proxy method may play a major role in our practical application. I have not thought deeply yet.
9. Future. prototype. return (value ):
10. Future. prototype. throw (error ):
11. Future. prototype. resolver ():
12. Future. prototype. detach ():
Compared with other APIs, the above four APIs are generally used in actual scenarios or functions. Both return and throw are scheduled by the resolver method. These three methods are very important and will work silently in the normal future use process, but I didn't come up with specific scenarios to use them separately, so there is no way to introduce it in detail. The detach method can only be used as a simplified version of the resolve method.