Koa.js is an expressive next-generation web framework written for Node.js by the people behind the Express and Connect frameworks. Koa.js leverages generators, which are a bleeding edge feature of JavaScript, and have not yet been made into stable versions of Node.js. Koa aims to use generators to save developers from the spaghetti of callbacks, making it less error-prone and thus more manageable.
With just 550 lines of code, Koa is an extremely light framework. Even after that, Koa packs in an elegant suite of methods such as content-negotiation, redirections, proxy support etc., giving you ease and speed of development along with the granular control over your node application.
Installing Node
Now before we begin, you will need to have at least Node version 0.11.x
or greater.
You can install the latest version of Node using the N module :
sudo npm install -g n sudo n stable
You can also use other community modules like nvm or build it from source. Please note that N is also a community module.
To run a JS file which makes use of generators, you need to provide the --harmony
flag when you run it.
For example, to run app.js
, enter in the following command:
node --harmony app.js
Or to save yourself from entering this flag every time, you can create an alias using the following command:
alias node="node --harmony"
Now to run your application using generators, just enter:
node app.js
Very good! Also keep in mind that all of the code in this article is available on GitHub. Feel free to fork and play with it.
Now to understand Koa, you must first understand generators which form the spine of the framework.
What Are Generators?
With ES-6, generators have finally landed in the magical land of JavaScript. If you have prior experience with generators in Lua, Python, Scheme, Smalltalk etc., then you would be glad to know that a very similar thing has been implemented in JavaScript.
Generators are first class co-routines in JavaScript which, simply put, introduces a pause and play interface in the language. Before generators, the whole script used to usually execute in a top to bottom order, without an easy way to stop code execution and resuming with the same stack later. Now lets get our hands dirty with some examples.
As per the current draft ES-6 specification, we need to use a different version of the function definition to create a generator function. It looks like this:
var generator_func = function* () { };
Here generator_func
is just an empty generator function.
So what we can do is use the yield
keyword in the function to stop the execution and save the current stack.
Here's a simple example demonstrating the Sum of an infinite AP:
var r = 3; function* infinite_ap(a) { for( ; ; ) { a = a + r; yield a; } } var sum = infinite_ap(5); console.log(sum.next()); // returns { value : 8, done : false } console.log(sum.next()); // returns { value : 11, done: false }
In the above code, we initially create an iterator instance named infinite_ap
which includes an infinite loop and if run under normal conditions, can freeze the execution.
Next we store an iterator instance in the sum
variable.
Now when we call sum.next()
, it returns { value: 8, done: false }
which means it stopped its execution on the yield
statement returning the value
as 'a' and done
as 'false' .
Here done
returns false until the execution is unfinished. Once the execution is complete (in the aforementioned case, it never happens) the function returns {value: undefined, done: true}
.
Here is a small modification of the previous code to demonstrate the end of execution:
var r = 3; function* infinite_ap(a) { for( var i = 0; i < 3 ; i++) { a = a + r ; yield a; } } var sum = infinite_ap(5); console.log(sum.next()); // returns { value : 8, done : false } console.log(sum.next()); // returns { value : 11, done: false } console.log(sum.next()); // returns { value : 14, done: false } console.log(sum.next()); //return { value: undefined, done: true }
In more complex programs, you would check and use the values returned and the done
status.
Note: Using yield
without function*
would lead to an early error.
Available Generators Methods
Here are some common methods that will come in handy when you deal with vanilla generators.
Each of the methods below is available only in a generator function and would throw an error otherwise.
next()
This is used to resume the execution along with passing an argument. If nothing is passed, then undefined gets passed as the first argument.
Example: sum.next(5);
throw()
This is used to throw an error or exception at any step. It makes error handling much easier. Throwing an error can result in stopping execution of the file, if it is not handled somewhere. The simplest way to handle an error is to use a try and catch statement. This method takes a single argument which can be anything.
Example: sum.throw(new Error("this is an error"));
Delegating yield
Generator delegation is used to yield a generator from within an existing generator and can be used to compose generators or even iterate over a generator.
On delegating to another generator, the current generator stops producing a value itself and starts yielding values of the delegated generator until it is exhausted. Upon exhaustion of the delegated generator, the generator resumes returning its own value.
It is very much like using a for-in
loop over a generator, but the exceptions of the delegated generator are propagated and thrown via the outer generator's throw
method and should be handled likewise. Here's an example:
var consoleLogThunk = function(msg) { return function() { console.log(msg); } } var generator = function*() { yield consoleLogThunk("Yo"); yield consoleLogThunk("Dawg"); yield consoleLogThunk("!!!"); } var delegator_function = function* () { yield consoleLogThunk("I yielded before delegated yield"); yield* generator(); yield consoleLogThunk("I yielded after delegated yield"); } var k = delegator_function(); k.next().value(); k.next().value(); k.next().value(); console.log(k.next()); // If you call k.next() , it will throw an Type error , as value is undefined which is not a function
Now that you have a brief understanding of generators in Javascript , you can use them for writing much clearer and less error-prone applications where you can block on I/O, without actually blocking the process.
Let's now move on to the installation of Koa and a very simple application based on Koa.js.
Koa.js:
Koa is an object which contains an array of middleware generator functions, all of which are composed and executed in a stack-like manner upon each request.
Installing Koa
In your project directory, execute the following command.
npm install koa --save
Koa will automatically be downloaded and saved in a package.json
file, if it exists.
Despite Koa's very small footprint, it includes methods for tasks like cache freshness, content-negotiation, redirections, proxy support etc., with no middleware bundled in.
Here's an example hello-world application:
var koa = require('koa'); var app = koa(); app.use(function *(){ this.body = "Hello World !!!"; }); app.listen(3000);
Koa's Control Flow
Now, Koa also implements downstreaming followed by upstreaming of control flow. At first it can be hard to gasp, but once you go through the example below, things will get clearer.
Here's an example of control flow in Koa:
var koa = require('koa')(); koa.use(function* (next) { //do something before yielding/passing to next generator function in line which will be 1st event in downstream console.log("A"); yield next; // do something when the execution returns upstream, this will be last event in upstream console.log("B"); }); koa.use(function* (next) { // do something before yielding/passing to the next generator function in line, this shall be 2nd event downstream console.log("C"); yield next; // do something when the execution returns upstream and this would be 2nd event upstream console.log("D"); }); koa.use(function* () { // do something before yielding/passing to next generator function in line. Here it would be last function downstream console.log("E"); this.body = "hey guys"; console.log("F"); // First event of upstream (from the last to first) }); koa.listen(3000);
The code above is pretty simple. Note that not all console.log
statements are required but they will help you to clearly understand the downstream and upstream execution flow of Koa.js .
Understanding the Examples' Execution Flow
When we run this application and open up localhost:3000
in the browser, we can observe that the console.logs
in the terminal are not in the order of A-B-C-D-E-F. Nor are they in the order of A-C-E-B-D-F.
The order is actually A-C-E-F-D-B which depicts the downstream of yields and upstream behavior of the execution in a Koa app.
You might notice that it is printed twice. This is due to a double request sent by the browser to fetch the favicon.
Tip: The koa.use(function)
adds the middleware function to the application.
In Conclusion
So that's it for part one of this tutorial on JavaScript generators and Koa.js. You've learned about most of the prerequisites such as what generators are, how to use use them, how to use delegating yield and how control flow works in Koa.js.
In the next part of this tutorial, we will dive deeper into Koa and learn how to build a CRUD application. If you have any questions or comments, feel free to contact me or just drop a comment below.
Comments