您的位置:首页 > Web前端 > Node.js

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: