koa源码解读
2018-10-06 08:06:38来源:博客园 阅读 ()
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-
转载请注明来源
扫描下方二维码,或者搜索 前端提高班 关注公众号,即可获取最新走心文章
记得把我设为星标或置顶哦
在公众号后台回复 前端资源 即可获取最新前端开发资源
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- koa2服务端使用jwt进行鉴权及路由权限分发 2019-08-14
- Vue.use()源码分析且执行后干什么了 2019-08-14
- koa2使用es7 的装饰器decorator 2019-08-14
- 浅析libuv源码-node事件轮询解析(3) 2019-05-13
- 浅析libuv源码-node事件轮询解析(2) 2019-05-08
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash