What is Koa?
相信很多人對NodeJS的第一次接觸就是Http Server,Koa是一個十分新潮的Http框架,他支援ES6甚至ES7的新特性,更容易debug,表達力更加強大,程式碼更加精簡。
相信很多人使用過Express。Express也是許多NPM套件的依賴,社群也很活躍,幾乎遇到的問題都已經有人遇過了,因此可以很容易找到解答。Koa是由Express的開發者另起爐灶做出來的框架,因為兩者在根本的概念上差異過大,所以才有此分歧。我不認為Koa會取代掉Express,因為Koa使用的門檻比較高一些,使用的新語法可能會對剛加入這個領域的人覺得困惑,但是如果之前就接觸過NodeJS的使用者,Koa絕對是值得嘗試的,因為他擁有許多迷人的優點。
Koa vs Express vs Connect
Feature | Koa | Express | Connect |
---|---|---|---|
Middleware Kernel | ✓ | ✓ | ✓ |
Routing | ✓ | ||
Templating | ✓ | ||
Sending Files | ✓ | ||
JSONP | ✓ |
由上表可知Koa是個相對精簡,並且高度模組化的框架,它預設只內涵一個中間件核心,我們需要什麼功能就將那個功能的中間件給安插進來,就像是玩積木一般的組裝一個最精簡的http server。這對於現代的Http server有著顯著的優勢,因為純前端的框架崛起,後端不再需要那麼多的功能,Koa在這方面能夠將我們所需要的功能,但是盡可能的最小化。
接下來的教學中會盡量涵蓋多點範例程式碼,並且下更多註解來幫助理解,因此如果沒有使用過其他前框架依舊可以從零開始學習。
注意: 因為Koa需要ECMAScript新特性的關係,我們NodeJS v7.6.0以上才得以執行。
如果NodeJS版本過舊,可以使用babel來進行轉譯。但是我個人更喜歡的方法是使用Typescript。
本篇文章也會以Typescript的方式來撰寫範例程式。
環境架設
使用yarn或者npm安裝koa套件yarn add koa @types/koa
或 npm install koa @types/koa --save
Hello World
我們來看一下Koa的Hello world
// Hello.ts
import Koa = require('koa'); // 引入koa模組
const app = new Koa(); // 建立koa實例
app.use(async (ctx) => { // 中間件
ctx.body = '<h1>Hello Koa</h1>'; // 回傳資料給與前端
});
app.listen(3000); // 監聽Port 3000
將檔案存成app.ts,然後使用ts-node app.ts
指令即可瀏覽http://localhost:3000/看到我們的預期結果。
Async function
使用async function是Koa的一大亮點,async function也是ES7新標準中對於ECMAScript的一項十分重大的改進。
阮一峰老師文章中有一句我非常認同的話: 异步编程的最高境界,就是根本不用关心它是不是异步
Async function是改善了ECMAScript多年以來異步編程的缺點,內化改良後衍生出來的精品,糅合了Generators以及Promise的各種優勢,讓我們能夠忘記自己正在寫的是異步程序,而達到與異步編程相同的效率。
在寫網站的同時我們會不斷地做一些需要異步處理的工作,例如檔案存取,資料庫的存取,這些操作如果使用Async function來控制流程,那是一件十分舒服的事,程式碼也會非常漂亮。
這邊不會深加討論async function的用法以及理論,我會將參考資料放在文末,可以參考這些資料來了解這個強悍的新功能。
我這邊僅用一個簡單的範例來比較一下使用async function與不使用async function所帶來程式碼的不同,進行一下比較。
我們寫一個類似于上個範例Hello Koa的例子,不過我們這次回傳的資料使用的是事先寫好的html檔,因此我們必須先讀取這個檔案,再將這個檔案的內容回傳回去給前端。
使用Async function
// async.ts
import Koa = require('koa');
import fs = require('fs');
import Q = require('q');
import path = require('path');
const app = new Koa();
const readFilePromise = Q.nfbind(fs.readFile); // 將Callback版本的readFile轉為Promise版本
app.use(async (ctx) => {
const data = await readFilePromise(path.join(__dirname, 'demo.html'), 'utf8'); // 使用await等待readFilePromise回傳回來的資料
ctx.body = data;
});
app.listen(3000);
不使用Async function
// non-async.ts
import express = require('express');
import fs = require('fs');
import path = require('path');
const app = express();
app.get('/', (req, res) => {
fs.readFile(path.join(__dirname, 'demo.html'), 'utf8', (err, data) => {
res.send(data);
});
});
app.listen(3000);
可以發現的是使用了async後,減少了callback使用的次數,因而降低了callback hell所帶來的危害。
Middleware中間件
中間件是Http server一個很重要的概念,他能讓我們有序列的處理一個http request,讓我們更容易做到模組化。
可以想象處理http request時就是將我們的request丟到一條流水線中,中間件就是流水線上的工人,當request到他面前時他可以經過一些處理再傳給下一個工人,直到產線最後這個request才算處理完畢。
在Koa中,我們使用use
這個實例函數來串接起我們的中間件們,use
的傳入值是一個callback,而這個callback中有一個context變數,以及一個可選的next函數。
- context(ctx): 裡頭存放了關於這次請求的request與response資訊,我們可以控制context物件來控制中間件的行為
- next函式: 透過呼叫next函式,將處理後的context轉移給下一個中間件。
因此我們思考一下下面這個範例
// middleware.ts
import Koa = require('koa');
const app = new Koa();
// Stage 1
app.use(async (ctx, next) => {
ctx.body = 'Stage 1 Pass\n';
next(); // 將context轉交給下個middleware
});
// Stage 2
app.use(async (ctx) => {
ctx.body += 'Stage 2 Pass\n'; // 我們不呼叫next(),因此這是這是最後一個middleware
});
// Stage 3
app.use(async (ctx) => {
ctx.body += 'Stage 3 Pass\n'; // 因為Stage 2沒有呼叫next(),因此這個中間件不會被執行
});
app.listen(3000);
前端的輸出結果是
Stage 1 Pass
Stage 2 Pass
由此可知,這個request的執行流程為
Koa受惠于這個機制,所以即使Koa kernel本身的功能雖然精簡,但是我們可以透過增加各種不同的中間件來完成許多複雜的任務,接下來的教學中,我們會逐步的介紹加上一些常用的中間件,例如Routing、Views、Session等功能,組合出功能完善的網頁。
References:
1. koa
2. async 函数的含义和用法
3. MDN: Async function
所有範例中的程式碼皆放置在koa-tutorial-sample專案中