共计 10016 个字符,预计需要花费 26 分钟才能阅读完成。
写在后面
Koa 应用了 ES6 标准的 generator 和异步编程是一个更轻量级 Web 开发的框架,Koa 的先天劣势在于 generator。因为是我集体的分享交换,所以 Node 根底、ES6 规范、Web 开发根底以及 Koa 的 ”Hello World” 程序都不在探讨,心愿各位小伙伴提出意见和领导。
PS:Koa 内核中没有捆绑任何中间件,但不必放心,Koa 领有极其强悍的拓展性,注释所有中间件都能够在 npm 官网下载安装,但国内域名装置会有一些限度,提供一个国内镜像装置办法,速度十分快,在间接 npm 模块失败的时候十分好用,应用 npm –registry=http://registry.npmjs.org install XXXXX –XX 命令装置,只须要在 install 前面加上要装置的中间件名称和相应的参数即可。
一、应用 Koa 搭建 Web 我的项目流程
1、Koa 我的项目创立
集体认为不论任何框架,Web 我的项目搭建必须的几个方面,页面、中间件、路由、会话和存储、日志、动态文件指定,以及谬误的解决。当然,网站开发不止这些货色,还有许多主题,比方实时通信,搜索引擎架构,权限管制,邮件优先队列,日志记录剖析,对 Web 开发还刚刚入门属于菜鸟级别,这里就不做深刻的探讨了。理解 Express 框架的小伙伴肯定晓得 Express 的部署过程,不论是通过 express-generator 生成还是 WebStorm 等编译器间接创立,它的目录构造大略是这样的:
|——app.js
|——bin
|——node_modules
|——package.json
|——public
|——routes
|——views
*app.js,是程序启动文件
*bin,寄存执行程序
*node_modules,寄存我的项目依赖库
*package.json,是配置和一些相干信息
*public,寄存动态文件(css,js,img)
*routes,寄存路由文件
*views,寄存前台页面文件
这些构造根本蕴含了上述提到的 Web 我的项目搭建的因素,然而目前相似 express-generator 的 Koa 部署工具 Koa-generator(非官方)并不欠缺并且集体测试存在些许谬误。其实 Koa-generator 也是仿造上述 express-generator 生成的目录,既然这样还不如手动创立目录来的痛快(generator- k 是另一款生成器,用下来感觉还行),在根目录新建 app.js 作为程序的启动文件,创立三个文件夹别离命名 public、routes 和 views,最初新建 package.json 文件寄存你的我的项目的一些信息。实现这些创立之后,用 npm 命令装置 Koa,这样的话一个根本的 Koa 框架就搭建好了,十分的的轻量级,它的目录构造如下:
|——app.js
|——node_modules
|——public
| |——img
| |——css
| |——js
|
|——routes
| |——index.js
| |——user.Js
|
|——views
| |——_layout.html
| |——index.html
|
|——package.json
Koa 我的项目运行:node --harmony app.js
必须加 --harmony,这样才会反对 ES6 语法。
2、Koa 日志
日志是我的项目 error 调试和日常保护的根本伎俩,Koa 有日志模块 Koa-logger,npm install Koa-logger 后应用 app.use(logger()); 命令程序就会在控制台主动打印日志,当然如果你对 Koa-logger 的格调不称心或者想要看到更多得信息也能够本人编辑代码实现有本人格调的日志打印。
例如:
auto map route -> [get]/authority/saveAddUser/
auto map route -> [get]/authority/searchUserInfo/
auto map route -> [get]/authority/updateUser/
auto map route -> [get]/authority/deletedUser/
auto map route -> [get]/authority/getSelectValues/
auto map route -> [get]/authority/saveAuthority/
最初呢,如果有须要,要把日志进行存储。
3、Koa 的错误处理
Koa 有 error 事件,当产生谬误时,能够通过该事件,对谬误进行对立的解决。
var Koa = require('koa');
var app = Koa();
app.on('error', function(err,ctx){console.log(err);
});
app.listen(3000);
下面这段代码在如果捕捉到谬误,页面会打印出“Internal Server Error”(这是 Koa 对谬误的默认解决)。这个谬误咱们在综合监控零碎中也常常见到,那么咱们显然无奈依据这条日志失去什么信息
TypeError: Cannot read property 'split' of undefined
at Object.Home.index (d:\test\route\home.js:143:31)
at GeneratorFunctionPrototype.next (native)
at Object.dispatch (d:\test\node_modules\koa-router\lib\router.js:97:44)
at GeneratorFunctionPrototype.next (native)
这些错误信息是怎么报进去的的呢,其实是 Koa-onerror 中间件,它优化错误信息,依据这些错误信息就能更好的捕捉到谬误。
Koa-onerror 应用办法:
var onerror = require('Koa-onerror');
onerror(app);
4、Koa 动态文件指定
Koa 动态文件指定中间件 Koa-static,npm install Koa-static 之后就能够应用 Koa-static 负责托管 Koa 利用内的动态资源。映射了动态文件目录,援用的时候间接去该目录下寻找资源,会缩小一些耗费。(不晓得讲的精确不精确,只是集体的了解)指定 public 为动态文件目录的代码如下:
var staticServer = require('koa-static');
var path = require('path');
app.use(staticServer(path.join(__dirname,'public')));
5、ejs 模板的应用
渲染页面须要一种模板,这里抉择格调靠近 html 的 ejs 模板。npm install Koa-ejs 后就能够在 Koa 框架中应用 ejs 模版。
var render = require('koa-ejs');
render(app, {root: path.join(__dirname, 'views'),
layout: '__layout',
viewExt: 'html',
cache: false,
debug: true
});
app.use(function *(){yield this.render('index',{layout:false});
});
6、Koa 路由设置
Koa 个极简的 web 框架,简略到连路由模块都没有装备。本人手写路由是这样的:
app.use(function *(){
// 我是首页
if(this.path==='/'){}});
应用更加弱小的路由中间件,Koa 中设置路由个别装置 Koa-router,Koa-router 反对五种办法
router.get()
router.post()
router.put()
router.del()
router.patch()
GET 办法举例:
var app = require('koa')();
var Router = require('koa-router');
var myRouter = new Router();
myRouter.get('/', function *(next) {yield this.render('index',{layout:false});
});
app.use(myRouter.routes());
app.listen(3000);
Koa-router 领有丰盛的 api 细节,用好这些 api,能够让页面代码更为优雅与可保护。
接管 query 参数
http://localhost:3000/?a=1(条件)
index.js
var router = require('koa-router')();
router
.get('/',function *(next){console.log(this.query);
yield this.render('index',{layout:false});
})
.get('/home',function *(ctx,next){ctx.render('home');
});
//ctx 为 Koa2.0 中反对
... ...
module.exports = router;
控制台打印:<-- GET /?a=1
{a: '1'}
{a: '1'}
接管 params 参数
http://localhost:3000/users/123(参数)router.get('/user/:id', function *(next) {console.log(this.params.id);
});
param() 用于封装参数解决中间件,当拜访 /detail/:id 路由时,会先执行 param() 定义的 generator function 逻辑。函数的第一个是路由参数的值,next 是中间件流程要害标识变量。
yield next;
示意执行下一个中间件。
app.param('id',function *(id,next){this.id = Number(id);
if (typeof this.id != 'number') return this.status = 404;
yield next;
}).get('/detail/:id', function *(next) {
// 我是详情页面
var id = this.id; //123
this.body = id;
});
7、Koa 中间件
Koa 的中间件很像 Express 的中间件,也是对 HTTP 申请进行解决的函数,然而必须是一个 Generator 函数即 function *(){} 语法,不然会报错。能够这么说,Nodejs 的 Web 程序中任何申请和响应都是中间件在操作。
app
.use(logger()) // 日志中间件
.use(serve(__dirname + '/public')) // 动态文件指定中间件
.use(router.routes()) // 路由中间件
.use(router.allowedMethods()); // 路由中间件
app.use 加载用于解决 http 申请的 middleware(中间件),当一个申请来的时候,会顺次被这些 middlewares 解决。执行的程序是你定义的程序。中间件的执行程序规定是相似“栈”的构造,所有须要执行的中间件都被一个一个放入“栈”中,当没有遇到 next()的时候,“栈”里边的这些中间件被逆序执行。
app.use(function *(next){
this; // is the Context
this.request; // is a Koa Request
this.response; // is a Koa Response
});
阐明:
•this 是上下文
•* 代表 es6 里的 generator
http 模型里的申请和响应
•this.request
•this.response
app.use() 到底产生了什么不堪设想的化学反应呢?
其实 app.use() 就干了一件事,就是将中间件放入一个数组,真正执行逻辑的是:app.listen(3000);
Koa 的 listen() 除了指定了 http 服务的端口号外,还会启动 http server,等价于:
var http = require('http');
http.createServer(app.callback()).listen(3000);
前面这种繁琐的模式有什么用呢?
一个典型的场景是启动 https 服务,默认 app.listen(); 是启动 http 服务,启动 https 服务就须要:
var https = require('https');
https.createServer(app.callback()).listen(3000);
二、异步编程
1、异步流程管制
异步编程对 JavaScript 语言太重要。JavaScript 只有一根线程,如果没有异步编程,基本没法用,非卡死不可。
以前,异步编程的办法,大略有上面四种。
回调函数
事件监听
公布 / 订阅
Promise 对象
JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把工作的第二段独自写在一个函数外面,等到从新执行这个工作的时候,就间接调用这个函数。它的英语名字 callback,直译过去就是 ” 从新调用 ”。
读取文件进行解决,是这样写的。
fs.readFile('/etc/passwd', function (err, data) {if (err) throw err;
console.log(data);
});
下面代码中,readFile 函数的第二个参数,就是回调函数,也就是工作的第二段。等到操作系统返回了 /etc/passwd 这个文件当前,回调函数才会执行。回调函数自身并没有问题,它的问题呈现在多个回调函数嵌套。假设读取 A 文件之后,再读取 B 文件,代码如下。
fs.readFile(fileA, function (err, data) {fs.readFile(fileB, function (err, data) {// ...});
});
不难想象,如果顺次读取多个文件,就会呈现多重嵌套。代码不是纵向倒退,而是横向发展,很快就会乱成一团,无奈治理。这种状况就称为 ” 回调函数噩梦 ”(callback hell)。Promise 就是为了解决这个问题而提出的。它不是新的语法性能,而是一种新的写法,容许将回调函数的横向加载,改成纵向加载。采纳 Promise,间断读取多个文件,写法如下。
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data){console.log(data.toString());
})
.then(function(){return readFile(fileB);
})
.then(function(data){console.log(data.toString());
})
.catch(function(err) {console.log(err);
});
下面代码中,我应用了 fs-readfile-promise 模块,它的作用就是返回一个 Promise 版本的 readFile 函数。Promise 提供 then 办法加载回调函数,catch 办法捕获执行过程中抛出的谬误。能够看到,Promise 的写法只是回调函数的改良,应用 then 办法当前,异步工作的两段执行看得更分明了,除此以外,并无新意。
Promise 的最大问题是代码冗余,原来的工作被 Promise 包装了一下,不论什么操作,一眼看去都是一堆 then,原来的语义变得很不分明。
那么,有没有更好的写法呢?
ECMAScript 6(简称 ES6)作为下一代 JavaScript 语言,将 JavaScript 异步编程带入了一个全新的阶段。异步编程的语法指标,就是怎么让它更像同步编程。
Koa 的先天劣势在于 generator。
generator 指的是
function* xxx(){}
是 es6 里的写法。
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}
yield 语句就是暂停标记,next 办法遇到 yield,就会暂停执行前面的操作,并将紧跟在 yield 前面的那个表达式的值,作为返回对象的 value 属性的值。当下一次调用 next 办法时,再持续往下执行,直到遇到下一个 yield 语句。如果没有再遇到新的 yield 语句,就始终运行到函数完结,将 return 语句前面的表达式的值,作为 value 属性的值,如果该函数没有 return 语句,则 value 属性的值为 undefined。当第一次调用 sum.next() 时 返回的 a 变量值是 5 + 3,同理第二次调用 sum.next(),a 变量值是 8 +3,晓得循环执行完结,返回 done:true 标识。大家有没有发现个问题,Koa 中 generator 的用法与上述 demo 演示的用法有十分大得差别,那是因为 Koa 中的 generator 应用了 co 进行了封装。
2、co 的应用
Ps:(这里只是简略介绍,后续能够作为一个专题来讲)
co 函数库是驰名程序员 TJ Holowaychuk 于 2013 年 6 月公布的一个小工具,用于 Generator 函数的主动执行。
比方,有一个 Generator 函数,用于顺次读取两个文件。
var gen = function* (){var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co 函数库能够让你不必编写 Generator 函数的执行器。
var co = require('co');
co(gen);
下面代码中,Generator 函数只有传入 co 函数,就会主动执行。
co 函数返回一个 Promise 对象,因而能够用 then 办法增加回调函数。
co(gen).then(function (){console.log('Generator 函数执行实现');
})
下面代码中,等到 Generator 函数执行完结,就会输入一行提醒。
为什么 co 能够主动执行 Generator 函数?
后面文章说过,Generator 函数就是一个异步操作的容器。它的主动执行须要一种机制,当异步操作有了后果,可能主动交回执行权。
两种办法能够做到这一点。
(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数外面交回执行权。
(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 办法交回执行权。
co 函数库其实就是将两种主动执行器(Thunk 函数和 Promise 对象),包装成一个库。应用 co 的前提条件是,Generator 函数的 yield 命令前面,只能是 Thunk 函数或 Promise 对象。
参考:http://www.ruanyifeng.com/blog/2015/05/co.html
3、Koa 中间件机制实现原理
应用 Koa 的同学肯定会有如下疑难:
- Koa 的中间件机制是如何实现?
- 为什么中间件必须是 generator function?
- next 实参指向是什么?为什么能够通过 yield next 能够执行下一个中间件?
- 为什么中间件从上到下执行完后,能够从下到上执行 yield next 后的逻辑?
通过实现简略的 Koa 框架(剥离除中间件外所有的逻辑)来解答上述问题,这个框架的名字叫 SimpleKoa:
var co = require('co');
function SimpleKoa(){this.middlewares = [];
}
SimpleKoa.prototype = {
// 注入个中间件
use: function(gf){this.middlewares.push(gf);
},
// 执行中间件
listen: function(){this._run();
},
_run: function(){
var ctx = this;
var middlewares = ctx.middlewares;
return co(function *(){
var prev = null;
var i = middlewares.length;
// 从最初一个中间件到第一个中间件的程序开始遍历
while (i--) {
// 理论 Koa 的 ctx 应该指向 server 的上下文,这里做了简化
//prev 将后面一个中间件传递给以后中间件
prev = middlewares[i].call(ctx, prev);
}
// 执行第一个中间件
yield prev;
})();}
};
写个 demo 印证下中间件执行程序:
var app = new SimpleKoa();
app.use(function *(next){
this.body = '1';
yield next;
this.body += '5';
console.log(this.body);
});
app.use(function *(next){
this.body += '2';
yield next;
this.body += '4';
});
app.use(function *(next){this.body += '3';});
app.listen();
执行后控制台输入:123456,对照 Koa 中间件执行程序,完全一致!寥寥几行代码,咱们就实现了 Koa 的中间件机制!这就是 co 的魔力。
三、Koa 中波及但本次没有讲的问题
1、Koa 中的 cookie 和 session(后续具体解说)
web 应用程序都离不开 cookie 和 session 的应用,是因为 Http 是一种无状态性的协定。保留用户状态信息的一种办法或伎俩,Session 与 Cookie 的作用都是为了放弃拜访用户与后端服务器的交互状态。
2、Koa 中 nosql(后续技术分享会具体解说)
mongodb 是一个基于文档的非关系型数据库,所有数据是从磁盘上进行读写的,其劣势在于查问性能比拟弱小,能存储海量数据。
redis 是内存型数据库,数据保留在内存中,通过 tcp 直接存取,劣势是速度快,并发高,毛病是数据类型无限,查问性能不强,个别用作缓存。它由 C 语言实现的,与 NodeJS 工作原理近似,同样以单线程异步的形式工作,先读写内存再异步同步到磁盘,读写速度上比 MongoDB 有微小的晋升,当并发达到肯定水平时,即可思考应用 Redis 来缓存数据和长久化 Session。
var mongoose = require('mongoose');
// 引入 mongoose 模块
mongoose.connect('mongodb://localhost/blog');
// 而后连贯对应的数据库:mongodb://localhost/test
// 其中,后面那个 mongodb 是 protocol scheme 的名称;localhost 是 mongod 所在的地址;// 端口号省略则默认连贯 27017;blog 是数据库的名称
// mongodb 中不须要建设数据库,当你须要连贯的数据库不存在时,会主动创立一个进去。module.exports = mongoose;
// 导出 mongoose 模块
var mongoose = require('../modules/db');
// 引入 mongoose 模块
var User = mongoose.model('User',{name: {type: String, match: /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/},
password: String
});
// 创立了一个名为 User 的 model
var user1 = new User({name:'12345@qqqqqq.com'});
user1.password = 'a5201314';
user1.save(function(err){if(err){console.log("save error");
}
});