koa 是有 express 原班人马打造的基于 node.JS 的下一代 web 开发框架. koa 1.0 使用 generator 实现异步, 相比于回调简单和优雅和不少. koa 团队并没有止步于 koa 1.0, 随着 node.JS 开始支持 async/await, 他们又马不停蹄的发布了 koa 2.0,koa2 完全使用 Promise 并配合 async/await 来实现异步, 使得异步操作更臻完美.
一, 快速开始
koa 使用起来非常简单, 安装好 node.JS 后执行以下命令安装 koa:
- NPM init
- NPM install --save koa
一个简单的 Hello World 程序开场,
- //index.JS
- const Koa = require('koa')
- const App = new Koa()
- App.use( async ctx => {
- ctx.body = 'Hello World'
- })
- App.listen(3000,()=>{
- console.log("server is running at 3000 port");
- })
在命令行执行
node index.JS
打开浏览器查看 http://localhost:3000 就可以看到页面输出的 Hello World.
中间件 middleware
Koa 中使用 App.use()用来加载中间件, 基本上 Koa 所有的功能都是通过中间件实现的.
中间件的设计非常巧妙, 多个中间件会形成一个栈结构 (middle stack), 以 "先进后出"(first-in-last-out) 的顺序执行. 每个中间件默认接受两个参数, 第一个参数是 Context 对象, 第二个参数是 next 函数. 只要调用 next 函数, 就可以把执行权转交给下一个中间件, 最里层的中间件执行完后有会把执行权返回给上一级调用的中间件. 整个执行过程就像一个剥洋葱的过程.
比如你可以通过在所有中间件的顶端添加以下中间件来打印请求日志到控制台:
- App.use(async function (ctx, next) {
- let start = new Date()
- await next()
- let ms = new Date() - start
- console.log('%s %s - %s', ctx.method, ctx.url, ms)
- })
常用的中间件列表可以在这里找到: https://GitHub.com/koajs/koa/wiki
二, koa 源码解读
打开项目根目录下的 node_modules 文件夹, 打开并找到 koa 的文件夹, 如下所示:
打开 lib 文件夹, 这里一共有 4 个文件,
application.JS - koa 主程序入口
context.JS - koa 中间件参数 ctx 对象的封装
request.JS - request 对象封装
response.JS - response 对象封装
我们这里主要看下 application.JS, 我这里摘取了主要功能相关的 代码如下:
- /**
- * Shorthand for:
- *
- * http.createServer(App.callback()).listen(...)
- *
- * @param {Mixed} ...
- * @return {Server}
- * @API public
- */
- listen(...args) {
- debug('listen');
- const server = http.createServer(this.callback());
- return server.listen(...args);
- }
- /**
- * Use the given middleware `fn`.
- *
- * Old-style middleware will be converted.
- *
- * @param {Function} fn
- * @return {Application} self
- * @API public
- */
- use(fn) {
- if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
- if (isGeneratorFunction(fn)) {
- deprecate('Support for generators will be removed in v3.' +
- 'See the documentation for examples of how to convert old middleware' +
- 'https://GitHub.com/koajs/koa/blob/master/docs/migration.md');
- fn = convert(fn);
- }
- debug('use %s', fn._name || fn.name || '-');
- this.middleware.push(fn);
- return this;
- }
- /**
- * Return a request handler callback
- * for node's native http server.
- *
- * @return {Function}
- * @API public
- */
- callback() {
- const fn = compose(this.middleware);
- if (!this.listenerCount('error')) this.on('error', this.onerror);
- const handleRequest = (req, res) => {
- const ctx = this.createContext(req, res);
- return this.handleRequest(ctx, fn);
- };
- return handleRequest;
- }
- /**
- * Handle request in callback.
- *
- * @API private
- */
- handleRequest(ctx, fnMiddleware) {
- const res = ctx.res;
- res.statusCode = 404;
- const onerror = err => ctx.onerror(err);
- const handleResponse = () => respond(ctx);
- onFinished(res, onerror);
- return fnMiddleware(ctx).then(handleResponse).catch(onerror);
- }
通过注释我们可以看出上面代码主要干的事情是初始化 http 服务对象并启动. 我们注意到 callback()方法里面有这样一段代码 :
const fn = compose(this.middleware);
compose 其实是 Node 模块 koa-compose, 它的作用是将多个中间件函数合并成一个大的中间件函数, 然后调用这个中间件函数就可以依次执行添加的中间件函数, 执行一系列的任务. 遇到 await next()时就停止当前中间件函数的执行并把执行权交个下一个中间件函数, 最后 next()执行完返回上一个中间件函数继续执行下面的代码.
它是用了什么黑魔法实现的呢? 我们打开 node_modules/koa-compose/index.JS, 代码如下 :
- function compose(middleware) {
- return function (context, next) {
- // last called middleware #
- let index = -1
- return dispatch(0)
- function dispatch(i) {
- if (i <= index) return Promise.reject(new Error('next() called multiple times'))
- index = i
- let fn = middleware[i]
- if (i === middleware.length) fn = next
- if (!fn) return Promise.resolve()
- try {
- return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
- } catch (err) {
- return Promise.reject(err)
- }
- }
- }
- }
乍一看好难好复杂, 没事, 我们一步一步的来梳理一下.
这个方法里面的核心就是 dispatch 函数(废话, 整个 compose 方法就返回了一个函数). 没有办法简写, 但是我们可以将 dispatch 函数类似递归的调用展开, 以三个中间件为例:
第一次, 此时第一个中间件被调用, dispatch(0), 展开:
- Promise.resolve(function(context, next){
- // 中间件一第一部分代码
- await/yield next();
- // 中间件一第二部分代码}());
很明显这里的 next 指向 dispatch(1), 那么就进入了第二个中间件;
第二次, 此时第二个中间件被调用, dispatch(1), 展开:
- Promise.resolve(function(context, 中间件 2){
- // 中间件一第一部分代码
- await/yield Promise.resolve(function(context, next){
- // 中间件二第一部分代码
- await/yield next();
- // 中间件二第二部分代码
- }())
- // 中间件一第二部分代码}());
很明显这里的 next 指向 dispatch(2), 那么就进入了第三个中间件;
第三次, 此时第二个中间件被调用, dispatch(2), 展开:
- Promise.resolve(function(context, 中间件 2){
- // 中间件一第一部分代码
- await/yield Promise.resolve(function(context, 中间件 3){
- // 中间件二第一部分代码
- await/yield Promise(function(context){
- // 中间件三代码
- }());
- // 中间件二第二部分代码
- })
- // 中间件一第二部分代码}());
此时中间件三代码执行完毕, 开始执行中间件二第二部分代码, 执行完毕, 开始执行中间一第二部分代码, 执行完毕, 所有中间件加载完毕.
再举一个例子加深下理解. 新建 index.JS 并粘贴如下代码:
- const compose = require('koa-compose')
- const middleware1 = (ctx, next) => {
- console.log('here is in middleware1, before next:');
- next();
- console.log('middleware1 end');
- }
- const middleware2 = (ctx, next) => {
- console.log('here is in middleware2, before next:');
- next();
- console.log('middleware2 end');
- }
- const middleware3 = (ctx, next) => {
- console.log('here is in middleware3, before next:');
- next();
- console.log('middleware3 end');
- }
- const middlewares = compose([middleware1, middleware2, middleware3])
- console.dir(middlewares())
在命令行输入 node index.JS 执行, 输出结果如下:
- here is in middleware1, before next:
- here is in middleware2, before next:
- here is in middleware3, before next:
- middleware3 end
- middleware2 end
- middleware1 end
- Promise {
- undefined
- }
可以看到每个中间件都按照 "剥洋葱" 的流程一次执行. 当我们初始化 App 对象并调用 App.use()时, 就是在不断往 App.middleware 数组里添加中间件函数, 当调用 App.listen()再执行组合出来的函数.
-END-
来源: https://www.cnblogs.com/lightzone/p/9746334.html