nodejs笔记杂谈
2015-12-03 14:05
591 查看
nodejs学习笔记-Express
安装$ npm install express --save $ npm install body-parser --save $ npm install cookie-parser --save $ npm install multer --save
解释
body-parser:node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。
cookie-parser: 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。
multer:node.js 中间件,用于处理 enctype=”multipart/form-data”(设置表单的MIME编码)的表单数据。
Generator梳理
Generator是ECMAScript6(代号harmory)中提供的新特性。在过去,封装一段运算逻辑的单元是函数。函数只存在“没有被调用”或者“被调用”的情况,不存在一个函数被执行之后还能暂停的情况,而Generator的出现让这种情况成为可能。定义
Generator的定义和函数的定义很相似,只比函数的定义多一个*号。这个星号只要出现在关键字function和函数名之间即可。如果是匿名函数,出现在function和参数列表的起始括号之间即可。
简单例子如下:
var compute = function* (a, b) { var sum = a + b; console.log(sum); var c = a - b; console.log(c); var d = a * b; console.log(d); var e = a / b; console.log(e); };
定义的Generator实际上可以理解为定义了一种特殊的数据结构,要得到Generator实例,还需要执行它一次:
var generator = compute(4, 2);
这样我们能得到一个Generator对象。Generator对象具有一个next方法。要使得定义中封装的代码逻辑得到执行,还得需要调用一次next方法才行。
generator.next();
调用之后的输出结果如下:
$ node --harmony-generators examples/compute.js 6 2 8 2
yield关键字
单独地介绍Generator没有太大价值,因为它除了更复杂外,功能与普通函数没有太大差别。真正让Generator具有价值的是yield关键字,这个yield关键字让Generator内部的逻辑能够切割成多个部分。下面是简单的示例:
var compute = function* (a, b) { var sum = a + b; yield console.log(sum); var c = a - b; yield console.log(c); var d = a * b; yield console.log(d); var e = a / b; console.log(e); };
加入yield关键字后,我们继续将其实例化,然后调用.next()方法:
var generator = compute(4, 2); generator.next();
可以看到输出如下:
$ node --harmony-generators examples/compute.js 6
上面的输出意味着代码执行到第一个yield关键字的时候就停止了。要让业务逻辑继续执行完,需要反复调用.next()。
var generator = compute(4, 2); generator.next(); // 6
generator.next(); // 2
generator.next(); // 8
generator.next(); // 2
可以简单地理解为yield关键字将程序逻辑划分成几部分,每次.next()执行时执行一部分。这使得程序的执行单元再也不是函数,复杂的逻辑可以通过yield来暂停。从朴素的角度来看,这类似于将一个函数的逻辑分拆为四个函数,但它们共享上下文。
yield除了切割逻辑外,它与.next()的行为息息相关。每次.next()调用时,返回一个对象,这个对象具备两个属性,其中一个属性是布尔型的done。它表示这个Generator对象的逻辑块是否执行完成。另一个属性是value,它来自于yield语句后的表达式的结果。我们将代码简单修改,可以看到效果:
var compute = function* (a, b) { var sum = a + b; yield sum; var c = a - b; yield c; var d = a * b; yield d; var e = a / b; return e; }; var generator = compute(4, 2); console.log(generator.next()); // { value: 6, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 8, done: false } console.log(generator.next()); // { value: 2, done: true }
上述是yield对.next()行为的影响,反之,.next()也能影响到yield。可以简单地猜测下如下代码中会打印出什么结果:
var compute = function* (a, b) {
var foo = yield a + b;
console.log(foo);
};
var generator = compute(4, 2); generator.next();
generator.next();
也许读者会以为是a + b的值,但是这里不是:默认情况下,这个foo打印出来是undefined。这里没有打印6而打印undefined是因为:第一个 generator.next() 开始执行 generator并在第一个 yield的地方暂停,这个时候 yield表达式的值为6,这个值加载在返回的对象中{value: 6, done: false}。第二个generator.next()恢复执行generator,这个时候因为没有传入任何参数,foo是undefined。这次generator执行完成,并返回第二次generator.next()返回的对象{value: undefined, done: true}。
所以如果想要最后打印出来的为a+b的值,我们需要将第一个next()获得的值,传给第二个next(),所以代码可以做如下改动:
var generator = compute(4, 2) var bar = generator.next().value generator.next(bar)
那么.next()如何影响yield的呢?答案在于可以通过.next()传递参数,赋值给yield关键字前面的变量声明。见下面的简单示例:
var compute = function* (a, b) {
var foo = yield a + b;
console.log(foo);
};
var generator = compute(4, 2); generator.next();
generator.next("Hello world!"); // Hello world!
所以,对于Generator而言,它不仅可以将逻辑的单元切分得更细外,还能在暂停和继续执行的间隔中,动态传入数据,使得代码逻辑可以更灵活。相比普通函数,Generator的特性相当令人期待。
看下执行顺序就能比较清楚Generator是怎么工作的了
Generator与异步编程
初见之下,Generator似乎与异步编程之间还八竿子打不着。但前文的高阶函数和Generator的介绍已经为我们准备好了基础。我们继续揭开Generator是如何与异步编程摩擦出闪亮的火花的。在拥有了yield关键字后,我们可以很巧妙地处理异步调用的回调函数。以顺序读取两个文件的场景为例:
fs.readFile('file1.txt', 'utf8', function (err, txt) { if (err) { throw err; } fs.readFile(txt, 'utf8', function (err, content) { if (err) { throw err; } console.log(content); }); });
如果我们要完成这两个操作,而且不以嵌套的方式进行,我们可以很自然想到以yield来分割两个操作。
var flow = function* () { var txt = yield fs.readFile('file1.txt', 'utf8'); var content = yield fs.readFile(txt, 'utf8'); console.log(content); };
这里虽然没有写入回调函数,但我们可以想象,如果回调函数执行的时候触发这个Generator执行一次.next(),然后将返回结果通过.next(result)这样的形式传入,这样尽管是异步调用,但代码编写形式已经近乎顺序式了。
想象是美好的,现实能否实现是另一回事。要完成上面的目的,需要做的事情有两步:需要进一步理解和解释。
需要在回调函数中置入逻辑,用于收集回调函数传递的数据。
通过.next()传入异步执行的结果,传递给yield,让业务流程继续进行。
改造异步方法
为了完成收集异步调用的结果数据,我们必须得借助高阶函数。如下是一个修改回调函数逻辑的函数:var helper = function (fn) { return function () { var args = [].slice.call(arguments); var pass; args.push(function () { // 在回调函数中植入收集逻辑 if (pass) { pass.apply(null, arguments); } }); fn.apply(null, args); return function (fn) { // 传入一个收集函数 pass = fn; }; }; };
这个函数的参数是一个异步调用函数,调用之后,得到一个新的函数,这个函数在重新整理了参数列表后,添加了一个实际被调用到的回调函数。这个新的函数执行后,会调用真正的异步函数,然后再次返回一个函数,最后返回的函数的作用是为了随时注入新的逻辑(pass)。在参数列表后添加的回调函数中,它会将结果传递给最终给到的函数。
用语言来解释这段代码有点吃力,我们以fs.readFile调用为实际例子,重点参见下文的注释:
var readFile = helper(fs.readFile); // => // function () { // var args = [].slice.call(arguments); // var pass; // args.push(function () { // 在回调函数中植入收集逻辑 // if (pass) { // pass.apply(null, arguments); // } // }); // fn.apply(null, args); // // return function (fn) { // 传入一个收集函数 // pass = fn; // }; // } var flow = function* () { var txt = yield readFile('file1.txt', 'utf8'); console.log(txt); }; var generator = flow(); var ret = generator.next(); // 执行readFile('file1.txt', 'utf8'); // ret.value => // function (fn) { // pass = fn; // }
可以看到上面的代码,我们已经成功的将flow这个Generator的第一部分代码执行起来,我们可以通过ret.value来尝试植入一段特殊的逻辑,同时在异步调用结束后,将数据取出,同时执行Generator的下一部分逻辑。完整代码如下:
var generator = flow(); var ret = generator.next(); // 执行readFile('file1.txt', 'utf8'); ret.value(function (err, data) { if (err) { throw err; } generator.next(data); });
通过这样置入特殊逻辑后,使得flow中的代码能够按期望顺利执行,通过yield巧妙地将回调函数得到的值转换为类似返回值。
为了让所有的Generator能适应这种情况,我们设计一个流程控制函数,用来专门控制此类操作。
设计流程控制函数
为了向TJ大神致敬,这个函数我们暂时命名为co。它要进行的操作是让Generator启动,在Generator暂停的时候,植入逻辑。简单实现如下:var co = function (flow) { var generator = flow(); var next = function (data) { var result = generator.next(data); if (!result.done) { result.value(function (err, data) { if (err) { throw err; } next(data); }); } }; next(); };
代码中通过递归调用来完成Generator中流程的执行,调用示例如下:
co(function* () { var txt = yield readFile('file1.txt', 'utf8'); console.log(txt); var txt2 = yield readFile('file2.txt', 'utf8'); console.log(txt2); });
执行结果如下:
$ node --harmony-generators flow.js I am file1. I am file2.
如此我们就完成从嵌套函数的写法转换为顺序式的编写,深度嵌套带来的“地狱之门”将成为历史。
并行执行
前面的例子中,已经成功地将嵌套代码转换为顺序的扁平的代码,但是异步调用之间仍然是串行执行,这使得我们无法享受到Node中并行I/O的好处。为此我们还需要改进co函数,使得异步调用能并行执行,同时依旧保证代码的顺序。为此,我们约定当yield后的表达式结果是一个数组时,表示里面为多个异步调用。简单的修改如下:
var co = function (flow) { var generator = flow(); var next = function (data) { var ret = generator.next(data); if (!ret.done) { if (Array.isArray(ret.value)) { var count = 0; var results = []; ret.value.forEach(function (item, index) { count++; item(function (err, data) { count--; if (err) { throw err; } results[index] = data; if (count === 0) { next(results); } }); }); } else { ret.value(function (err, data) { if (err) { throw err; } next(data); }); } } }; next(); };
调用示例如下,当为数组时,应当并行执行:
co(function* () { var results = yield [readFile('file1.txt', 'utf8'), readFile('file2.txt', 'utf8')]; console.log(results[0]); console.log(results[1]); var file3 = yield readFile('file3.txt', 'utf8'); console.log(file3); });
执行结果如下:
$ node --harmony-generators parallel.js I am file1. I am file2. I am file3.
为了验证异步调用是并行进行的,我们换用定时器来测试:
var _sleep = function (ms, fn) { setTimeout(fn, ms); }; var sleep = helper(_sleep); co(function* () { console.time('sleep1'); yield sleep(1000); yield sleep(1000); console.timeEnd('sleep1'); console.time('sleep2'); yield [sleep(1000), sleep(1000)]; console.timeEnd('sleep2'); });
执行结果如下:
$ node --harmony-generators sleep.js sleep1: 2004ms sleep2: 1005ms
从sleep2的输出来看,yield关键字后的数组中的所有异步调用得到并行执行。
小结
至此,我们小结一下通过Generator进行流程控制的几个要点。首先,每个异步方法都需要标准化为yield关键字能接受的方法,使我们有机会注入特殊逻辑,这个过程被TJ称为thunkify。其次,需要巧妙地将异步调用执行完成得到的结果通过.next()传递给下一段流程。最后,需要递归地将业务逻辑执行完成。需要注意的是yield只能暂停Generator内部的逻辑,它并不是真正暂停整个线程,Generator外的业务逻辑依然会继续执行下去。
Generator的出现,使得流程控制可以更细腻,通过co和suspend库,我们几乎已经完全实现了流程控制的线性处理,同时还能享受到并行异步的性能提升,在不损失性能的情况下,大大提升了编程体验。简单而言,即使只为这个特性,ECMAScript6也值得期待。
*参考文章:
http://www.infoq.com/cn/articles/generator-and-asynchronous-programming/*
https://cnodejs.org/topic/542953d42ca9451e1bf3c251
相关文章推荐
- Node.js 学习一(Node.js 安装)
- 开发日记——LastWord
- nodejs事件循环
- Node.js REPL(交互式解释器) Node.js 回调函数
- Zookeeper管理工具node-zk-browser
- How do I get started with Node.js
- Node.js 环境配置记录
- Notepad++ 配置 Node.js 开发环境
- node.js
- [Leetcode] Swap Nodes in Pairs
- Node.js + Express + Ubuntu
- Leetcode177: Count Complete Tree Nodes
- node js 调试
- nodeJs+jquery实现聊天插入表情功能
- leetcode 237 Delete Node in a Linked List C++
- 解决Jenkins自动构建nodejs项目无法完成的问题
- 转:配置nodemanager启动weblogic服务器
- node异步转同步,KO 恶魔金字塔
- nodejs创建一个应用
- ATS统计量proxy.node.client_throughput_out的单位调研