recently tried to use a bit of KOA, and here to record the use of the experience.
Note: This article is based on the reader already understand generator and promise for the premise of writing, because generator and promise can write a blog to explain the introduction, so it is not here to repeat. A lot of online information, you can check it yourself.
KOA is a smaller, Nodejs platform-based next-generation Web development framework created by Express's original squad. Koa's subtlety lies in its use of generator and promise to achieve a more interesting middleware system, KOA Middleware is a series of generator functions of the object, executed somewhat similar to the structure of the stack, followed by execution. It's also similar to the Python framework's middleware system, which was formerly called The Onion model when the big God shared it.
When a request comes over, it will be processed by each middleware in turn, the signal of the middleware jump is yield next, when the middleware is finished, it will not execute yield next, and then the remaining logic of the preceding middleware is executed in reverse order. An example of a direct previous website:
varKOA = require (' KOA '));varApp =KOA ();//Response-time MiddlewareApp.use (function*(next) {varStart =NewDate; Yield next; varms =NewDate-start; This. Set (' X-response-time ', Ms + ' MS ');});//Logger MiddlewareApp.use (function*(next) {varStart =NewDate; Yield next; varms =NewDate-start; Console.log ('%s%s-%s ', This. method, This. URL, ms);});//Response MiddlewareApp.use (function*(){ This. BODY = ' Hello world ';}); App.listen (3000);
The above execution order is: request ==> response-time middleware ==> Logger middleware ==> response middleware ==> Logger middleware ==> response-time middleware ==> response.
A more detailed description is: request come in, advanced to Response-time middleware, execute var start = new Date; then encounter yield next, then pause the execution of Response-time middleware, jump into the logger middleware, the same, finally into the response middleware, the response middleware does not yield next code, then began to reverse order execution, That is, first back to the logger middleware, execute the code after yield next, and then go back to the Response-time middleware after yield next code.
At this point, the entire KOA middleware execution is complete, the entire middleware execution process is quite interesting.
While the KOA middleware is running under the CO function, and TJ Big God's Co function can be asynchronous synchronization, also said, write KOA middleware When you can write this, take the above demo final response middleware can be changed to this:
App.use (function*() { varnew Promise (function(resolve) { fs.readfile (function(err, data) { resolve (data); }) }); this. Body = text;});
By promise, the obtained file data can be passed through the resolve function to the outermost text, and the entire asynchronous operation becomes a synchronous operation.
For example, using MongoDB to do a database query function, it can be written so that the entire data query is originally asynchronous operation, can also become synchronous, because MongoDB official driver interface provides the function of returning promise, In the CO function, only yield can be used to directly synchronize the asynchronous, no longer have to write the disgusting callback nested.
var Mongoclient = require ("MongoDB"). Mongoclient;app.use (function *() { var db = Yield mongoclient.connect (' mongodb:/ /127.0.0.1:27017/myblog '); var collection = db.collection (' document '); var result = yield Collection.find ({}). ToArray (); Db.close ()});
The CO function of TJ is like a magic, which turns all asynchrony into synchronization and looks like a big one. But what the CO function does is not really complicated.
The entire CO function is plain, that is, using promise recursive call to Generator's next method, and in the latter call, the previous return of the data passed in, until the call is complete. The Co function also assembles the functions, generator, and arrays of non-promise objects into promise objects. So can not only after yield can be connected to promise, but also can be connected to generator objects and so on.
You implement a simple co function, pass in a generator, get the Generator function object, then define a next method for recursion, execute Generator.next () in the next method, and pass in data. After execution Generator.next () Gets the object to {value:xx, done:true|false}, if done is true, the generator has been iterated and exited.
Otherwise, assuming the current execution to yield new Promise (), That is, the returned Result.value is the Promise object, executes the promise's then method directly, and executes the NEX in the onfulfilled callback of the then method (that is, after the asynchronous execution in promise, which triggers the callback function when the resolve is called). The T method is recursive, and the incoming data in the onfulfilled is passed into the next method, and the data can be passed in the next Generator.next ().
//Easy implementation of COfunctionCo (generator) {varGen =generator (); varNext =function(data) {varresult =gen.next (data); if(Result.done)return; if(Result.valueinstanceofPromise) {Result.value.then (function(d) {next (d); }, function(Err) {next (ERR); }) }Else{next (); } }; Next ();}
Write a demo test:
//TestCofunction*(){ varText1 = yieldNewPromise (function(Resolve) {SetTimeout (function() {Resolve ("I am Text1"); }, 1000); }); Console.log (Text1); varText2 = yieldNewPromise (function(Resolve) {SetTimeout (function() {Resolve ("I am Text2"); }, 1000); }); Console.log (Text2);});
Operation Result:
Run successfully !
Now that we know the principle of the CO function, how does the KOA middleware be implemented? The entire implementation principle is to put all generator in an array to save, and then make the corresponding chain call to all generator.
At first it was self-fulfilling, and the approximate principle was as follows:
Using the array, each time the use method is executed, the generator passed into the gens arrays, and then, when executed, first define a generator index, jump Mark NE (that is, yield next in the next), There is also an array of GS that is used to hold the Generator function object. Then get the current middleware generator, and get to the Generator function object, put the function object in the GS array to save, and then execute Generator.next ().
Then according to the returned value, do different processing, if it is promise, then the same as the above co function, in its onfulfilled callback to execute the next generator.next (), if it is NE, that is, the current execution to yield next, Instructions to jump to the next middleware, this time to index++, and then from the gens array to get the next middleware to repeat the operation of the previous middleware.
When there is no yield next in the implemented middleware, and when the generator has been executed, that is, the return done is true, then the reverse order is executed, The function object that was used to save the generator is obtained from the GS array to the previous Generator function object, and then the next method of the generator is executed. Until all execution is complete.
The whole process is like, first into the stack, then out of the stack operation.
//simple implementation of KOA middleware effectvarGens = [];functionUse (generetor) {Gens.push (generetor);}functionTrigger () {varindex = 0; varNE = {}; varGS =[], G; Next (); functionNext () {//gets the current middleware, passing in the next tag, that is, when yield next processes the next middleware varGen =Gens[index] (NE); //Save the instantiated middlewareGs.push (gen); Co (gen)}functionCo (gen, data) {if(!gen)return; varresult =gen.next (data); //when the current generator middleware executes, the index is executed minus one, the upper level of the middleware is obtained and the execution if(result.done) {index--; if(g =Gs[index]) {Co (g); } return; } //if executed to promise, then recursion is performed when the promise is completed if(Result.valueinstanceofPromise) {Result.value.then (function(data) {Co (gen, data); }) }Else if(Result.value = = =NE) { //executes the next middleware when yield next is encounteredindex++; Next (); }Else{Co (gen); } }}
And then write a demo test:
//TestUse (function*(next) {varD = YieldNewPromise (function(Resolve) {SetTimeout (function() {Resolve ("Step1") }, 1000) }); Console.log (d); Yield next; Console.log ("Step2");}); Use (function*(Next) {Console.log ("Step3"); Yield next; varD = YieldNewPromise (function(Resolve) {SetTimeout (function() {Resolve ("Step4") }, 1000) }); Console.log (d);}); Use (function*(){ varD = YieldNewPromise (function(Resolve) {SetTimeout (function() {Resolve ("Step5") }, 1000) }); Console.log (d); Console.log ("Step6");}); Trigger ();
Operation Result:
Run successfully!
Above is just my own feel of the implementation of the principle, but in fact koa own implementation more streamlined, after looking at the source of KOA, also probably realized a bit, in fact, the above that the CO function is properly modified, and then use a while loop, all generator chained together, And then put in the CO function to yield. Paste the following source code:
varGens = [];functionUse (generetor) {Gens.push (generetor);}//implementing the CO functionfunctionCo (flow, isgenerator) {varGen; if(isgenerator) {Gen=flow; } Else{Gen=flow (); } return NewPromise (function(resolve) {varNext =function(data) {varresult =gen.next (data); varValue =Result.value; //If the call is complete, call resolve if(Result.done) {resolve (value); return; } //if the generator is followed by yield, the incoming co is recursive and the promise is returned if(typeofValue.next = = = "function" &&typeofValue.Throw= = = "function") {Value= Co (value,true); } if(value.then) {//when promise executes, call next to process the next yieldValue.then (function(data) {next (data); }) } }; Next (); });}functionTrigger () {varPrev =NULL; varm =gens.length; Co (function*(){ while(m--){ //forming a chain-type generatorPrev = Gens[m].call (NULL, prev); } //perform the outermost generator methodyield prev; })}
The execution result is also no problem, running demo and running results with the same, it is not posted out.
By the way, the project that I use to study KOA: Https://github.com/whxaxes/myblog interested can read
The three code written above is also on GitHub:
Https://github.com/whxaxes/myblog/blob/master/myco.js
Https://github.com/whxaxes/myblog/blob/master/mykoa.js
Https://github.com/whxaxes/myblog/blob/master/mykoa_2.js
And an article that can help understand: http://www.infoq.com/cn/articles/generator-and-asynchronous-programming/
Analysis of KOA Framework practice and middleware principle