Analysis of KOA Framework practice and middleware principle
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:
var koa = require (' KOA '); var app = Koa ();//Response-time Middleware App.use (function * (next) { var start = new Date; Yield next; var ms = new Date-start; This.set (' x-response-time ', Ms + ' MS '); /logger Middleware App.use (function * (next) { var start = new Date; Yield next; var ms = new Date-start; Console.log ('%s%s-%s ', This.method, This.url, MS);}); /Response Middleware App.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 meet 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 logger middleware, Execute the code after yield next, and then return to the code after the Response-time middleware executes yield next.
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* () { var text = yield new Promise (function (resolve) { fs.readfile ('./index.html ', ' utf-8 ', 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 ().
Co Simple realization function Co (generator) { var gen = generator (); var next = function (data) { var result = gen.next (data); if (Result.done) return; if (result.value instanceof Promise) { result.value.then (function (d) { next (d); }, Function (err) { Next (Err); }) } else { next (); } }; Next ();}
Write a demo test:
Testco (function* () { var Text1 = yield new Promise (function (resolve) { setTimeout (function () { Resolve (" I am Text1 "); } ); Console.log (Text1); var text2 = yield new Promise (function (resolve) { setTimeout (function () { resolve ("I am Text2"); }, 1000); c10/>}); 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 effect var gens = [];function use (generetor) {Gens.push (generetor);} Function trigger () {var index = 0; var ne = {}; var GS = [], G; Next (); function Next () {//Gets the current middleware, passing in next tag, that is, when yield next processes the next middleware var gen = Gens[index] (NE); Save the instantiated middleware Gs.push (gen); Co (GEN)} function Co (gen, data) {if (!gen) return; var result = Gen.next (data); When the current generator middleware executes, the index will be executed minus one, get the upper level of the middleware and execute if (result.done) {index--; if (g = Gs[index]) {Co (g); } return; }//If executed to Promise, when Promise executes and then the recursive if (result.value instanceof Promise) {Result.value.then (funct Ion (data) {Co (gen, data); })}else if (result.value = = = NE) {//When encountering yield next, executes the next middleware index++; Next (); }else {Co (gen); } }}
And then write a demo test:
Testuse (function* (next) { var d = yield new Promise (function (resolve) { setTimeout (function () { Resolve ( "Step1") } ); Console.log (d); Yield next; Console.log ("Step2");}); Use (function* (next) { console.log ("Step3"); Yield next; var d = yield new Promise (function (resolve) { setTimeout (function () { resolve ("Step4") }, +) }) ; Console.log (d);}); Use (function* () { var d = yield new Promise (function (resolve) { setTimeout (function () { resolve ("Step5" )} ); 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:
var gens = [];function use (generetor) {Gens.push (generetor);} Implement CO function functions Co (flow, isgenerator) {var Gen; if (isgenerator) {gen = flow; } else {Gen = flow (); } return new Promise (function (resolve) {var next = function (data) {var result = Gen.next (data); var value = 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 (typeof Value.next = = = "function" && ty peof Value.throw = = = "function") {value = Co (value, true); } if (Value.then) {///when promise execution is complete, call next to process the next yield value.then (function (data) { Next (data); }) } }; Next (); });} Function trigger () {var prev = null; var m = gens.length; Co (function* () {while (m--) { Form chain Generator prev = Gens[m].call (null, prev); }//Perform outermost generator method yield 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