先不谈概念、定义,咱们从实际用例切入。node
咱们如今要作的,是一个基于nodejs+mongoDB的web应用,其中须要一个web页面,显示用户的列表。就这样,需求就这么简单。web
首先,咱们选用了koa做为web层的框架,而且使用mongodb的原生driver访问数据库。mongodb
var koa = require('koa'); var route = require('koa-route'); var mongo = require('mongodb').MongoClient; var app = koa(); // 对/user地址的请求,会被路由到userlist这个函数处理 app.use(route.get('/user',userlist)); app.listen(8002); function* userlist() { var _this = this; // 先链接mongodb,这是异步IO的过程,因此要回调 mongo.connect('mongodb://localhost/koa-test', function(err, db){ // 链接数据库成功后,获取user这个集合 var users = db.collection('user'); // 对user集合进行查询,这也是一个异步IO过程,因此也产生回调 users.find({}).toArray(function(err, docs) { // 查询完毕,能够关闭数据库链接 db.close(); // 将查询结果写到返回的body中 _this.body = ''; for(var i=0; i<docs.length; i++) { _this.body += '<h2>' + docs[i].username + '</h2>'; } }); }); }
上述代码,有两个异步过程,所以产生两个回调函数。为确保异步操做正确完成,最终对返回body的赋值要放到最里面的回调中:数据库
mongo.connect(url, function(){ ... users.find().toArray(function(){ ... _this.body = ... }) });
但代码实际运行仍是会报错:app
Error: Can't set headers after they are sent.框架
这依然是异步形成的问题:koa框架对userlist函数的执行,是不会被connect等异步操做所阻塞,也即mongo.connect触发以后,userlist函数会直接往下走,koa发现没有逻辑了,就把请求返回了。而等mongo.connect、users.find完成后(进入回调函数),请求早已返回,此时对body和header的赋值都将是非法的。koa
固然,这个问题,也是因为使用koa形成的:若是不实用koa,返回操做是由代码声明,那么就能够在回调中才进行返回操做;而使用了koa,则是由框架执行route handler(定制代码),而后由框架执行返回。异步
要解决这个问题,就要把异步非阻塞,变成异步阻塞。函数
这个时候,终于入正题了:ES6-Generatorui
Generator是什么暂且不详述,咱们先看看它如何为咱们解决问题:
var koa = require('koa'); var route = require('koa-route'); var monk = require('monk'); var wrap = require('co-monk'); var app = koa(); // 对/user地址的请求,会被路由到userlist这个函数处理 app.use(route.get('/user',userlist)); app.listen(8002); function* userlist() { // 链接数据库,非阻塞的异步过程 var db = monk('localhost/koa-test'); // 获取user集合 var users = wrap(db.get('user')); // 对user集合进行查询,非阻塞异步过程 var userlist = yield users.find({}); // 将查询结果写到返回的body中 this.body = ''; for(var i=0; i<userlist.length; i++) { this.body += '<h2>' + userlist[i].username + '</h2>'; } db.close(); }
和以前的代码最大的区别,是两个异步操做(数据库链接,集合查询)的回调函数没有了,代码以更符合人类思惟的顺序执行方式,也即代码从横向扩展变成了竖向扩展。
其中,咱们要集中看的是这句
var userlist = yield users.find({});
这里在执行users.find以前,多了一个关键字yield。就是它,让咱们的异步代码有了阻塞执行的功能。 若是这里咱们省去yield,变成这样:
var userlist = users.find({});
代码执行是会报错:
TypeError: Cannot read property 'username' of undefined
这说明,没有yield关键字的时候,users.find异步非阻塞的执行,在等待磁盘IO的时候,下面的逻辑已经在执行
for(var i=0; i<userlist.length; i++) { this.body += '<h2>' + userlist[i].username + '</h2>'; }
此时查询的磁盘IO还没完成,userlist变量就还没被赋值,因此是undefined,就出现上述的报错了。
至于这一句:
var db = monk('localhost/koa-test');
之因此不须要使用yield关键字,是由于咱们使用了monk这个库,monk内部已经进行了包装,因此在外部调用的时候无须加上yield,具体这里不详述。
至此,咱们带出的是ES6-Generator中很是重要的yield关键字。要理解generator,必须理解这个yield,除了上面提到的,yield会让异步非阻塞代码变成异步阻塞代码,其实还有一个更好理解的比喻:断点。
看这样一个示例代码:
function test() { console.log('1'); console.log('2'); console.log('3'); }
就是顺序打印一、二、3,简单到没朋友。加上yield以后呢?
function* test() { console.log('1'); yield 1; console.log('2'); yield 2; console.log('3'); }
代码中间插了两行yield,表明什么呢?
这是否是就像,咱们调试代码的时候,给插的断点 ?
固然,断点这个比喻,只是表象上比较相像,实质原理仍是有很是大差别。
要注意,function后面多了一个星号,这样是代表这个函数将变成一个生成器函数,而不是一个普通函数了。意思就是,test这个函数,将不能被这样执行
test();
但能够得到一个生成器
var gen = test(); // gen就是一个生成器了
而后,生成器能够经过next()来执行运行
gen.next();
也就是上面说的,让函数继续运行的指令。
由此能够看出,yield是如何将异步非阻塞代码,变成 异步阻塞代码。
P.S. generator是要配合执行器使用的,回顾mongodb的示例代码中并无使用执行器,这是由于使用了koa框架。koa自封装了一个co做为generator的执行器,在koa框架下generator会被co自动执行,因此开发者无需关注这些细节,也所以代码变得更为简洁。
generator是ES6里面一个很重要的部分,目的就是解决JS异步代码带来的问题。generator包含不少概念,如上述的执行器,本文只是从应用场景出发,简单解释了generator是如何解决这些异步代码问题的,里面更深层次的原理未有完整覆盖,若是须要,能够搜索ruanyf的博客查看。