Mongoose六.属性方法

1.ObjectId类型


ObjectId简述

存储在mongodb集合中的每个文档(document)都有一个默认的主键_id,这个主键名称是固定的,它可以是mongodb支持的任何数据类型,默认是ObjectId。该类型的值由系统自己生成,从某种意义上几乎不会重复,生成过程比较复杂,有兴趣的朋友可以查看源码。

使用过MySQL等关系型数据库的友友们都知道,主键都是设置成自增的。但在分布式环境下,这种方法就不可行了,会产生冲突。为此,MongoDB采用了一个称之为ObjectId的类型来做主键。ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,一次代表:

4字节:UNIX时间戳
3字节:表示运行MongoDB的机器
2字节:表示生成此_id的进程
3字节:由一个随机数开始的计数器生成的值

1
2
var mongoose = require('mongoose');
var tSchema = new mongoose.Schema({}); //默认_id:ObjectId类型

每一个文档都有一个特殊的键“_id”,这个键在文档所属的集合中是唯一的。

2.添加属性


Schema添加属性值

前面我们已经讲述了如何定义一个Schema并赋予某些属性值,那能不能先定义后添加属性呢,答案是可以的,如下所示:

1
2
3
var mongoose = require('mongoose');
var TempSchema = new mongoose.Schema;
TempSchema.add({ name: 'String', email: 'String', age: 'Number' });

3.实例方法


实例方法

有的时候,我们创造的Schema不仅要为后面的Model和Entity提供公共的属性,还要提供公共的方法.那怎么在Schema下创建一个实例方法呢,请看示例:

1
2
3
4
5
6
7
8
var mongoose = require('mongoose');
var saySchema = new mongoose.Schema({name : String});
saySchema.method('say', function () {
console.log('Trouble Is A Friend');
})
var say = mongoose.model('say', saySchema);
var lenka = new say();
lenka.say(); //Trouble Is A Friend

4.静态方法


Schema静态方法

接下来将讲述怎么为Schema创建静态方法。如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var mongoose = require("mongoose");
var db = mongoose.connect("mongodb://127.0.0.1:27017/test");
var TestSchema = new mongoose.Schema({
name : { type:String },
age : { type:Number, default:0 },
email: { type:String, default:"" },
time : { type:Date, default:Date.now }
});
TestSchema.static('findByName', function (name, callback) {
return this.find({ name: name }, callback);
});
var TestModel = db.model("test1", TestSchema );
TestModel.findByName('tom', function (err, docs) {
//docs所有名字叫tom的文档结果集
});

5.追加方法


Schema追加方法

有时场景的需要,我们甚至可以为Schema模型追加方法。

为Schema模型追加speak方法,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var mongoose = require("mongoose");
var db = mongoose.connect("mongodb://127.0.0.1:27017/test");
var TestSchema = new mongoose.Schema({
name : { type:String },
age : { type:Number, default:0 },
email: { type:String, default:"" },
time : { type:Date, default:Date.now }
});
TestSchema.methods.speak = function(){
console.log('我的名字叫'+this.name);
}
var TestModel = db.model('test1',TestSchema);
var TestEntity = new TestModel({name:'Lenka'});
TestEntity.speak();//我的名字叫Lenka

Mongoose五.游标

1.简介


数据库使用游标返回find的执行结果。客户端对游标的实现通常能够对最终结果进行有效的控制。可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者是执行其他一些强的操作。

最常用的查询选项就是限制返回结果的数量(limit函数)、忽略一点数量的结果(skip函数)以及排序(sort函数)。所有这些选项一点要在查询被发送到服务器之前指定。

2.限制返回数量


limit函数的基本用法

在查询操作中,有时数据量会很大,这时我们就需要对返回结果的数量进行限制,那么我们就可以使用limit函数,通过它来限制结果数量。

1.限制数量:find(Conditions,fields,options,callback);

1
2
3
Model.find({},null,{limit:20},function(err,docs){
console.log(docs);
});

如果匹配的结果不到20个,则返回匹配数量的结果,也就是说limit函数指定的是上限而非下限。

3.跳过结果数量


skip函数的基本用法

skip函数和limit类似,都是对返回结果数量进行操作,不同的是skip函数的功能是略过指定数量的匹配结果,返回余下的查询结果。如下示例:

1.跳过数量:find(Conditions,fields,options,callback);

1
2
3
Model.find({},null,{skip:4},function(err,docs){
console.log(docs);
});

如果查询结果数量中少于4个的话,则不会返回任何结果。

4.排序


sort函数的基本用法

sort函数可以将查询结果数据进行排序操作,该函数的参数是一个或多个键/值对,键代表要排序的键名,值代表排序的方向,1是升序,-1是降序。

1.结果排序:find(Conditions,fields,options,callback);

1
2
3
Model.find({},null,{sort:{age:-1}},function(err,docs){
//查询所有数据,并按照age降序顺序返回数据docs
});

sort函数可根据用户自定义条件有选择性的来进行排序显示数据结果。

5.小结


简单回顾:

  1. limit函数:限制返回结果的数量。

  2. skip函数:略过指定的返回结果数量。

  3. sort函数:对返回结果进行有效排序。

Mongoose四.高级查询

1.条件查询


接下来我们来看一些在查询时会用的常用操作符,通过操作符的使用,我们就可对数据进行更细致性的查询,一起来看一下吧。

“$lt”(小于),”$lte”(小于等于),”$gt”(大于),”$gte”(大于等于),”$ne”(不等于),”$in”(可单值和多个值的匹配),”$or”(查询多个键值的任意给定值),”$exists”(表示是否存在的意思)”$all”。

2.大、小于


$gt、$lt简述

查询时我们经常会碰到要根据某些字段进行条件筛选查询,比如说Number类型,怎么办呢,我们就可以使用$gt(>)、$lt(<)、$lte(<=)、$gte(>=)操作符进行排除性的查询,如下示例:

1
2
3
4
5
6
7
8
9
Model.find({"age":{"$gt":18}},function(error,docs){
//查询所有nage大于18的数据
});
Model.find({"age":{"$lt":60}},function(error,docs){
//查询所有nage小于60的数据
});
Model.find({"age":{"$gt":18,"$lt":60}},function(error,docs){
//查询所有nage大于18小于60的数据
});

如果我们要对类似age字段的数据进行筛选,使用$gt、$lt是不是很方便快捷呢!

3.不匹配


$ne简述

$ne(!=)操作符的含义相当于不等于、不包含,查询时我们可通过它进行条件判定,具体使用方法如下:

1
2
3
4
5
6
Model.find({ age:{ $ne:24}},function(error,docs){
//查询age不等于24的所有数据
});
Model.find({name:{$ne:"tom"},age:{$gte:18}},function(error,docs){
//查询name不等于tom、age>=18的所有数据
});

$ne可以匹配单个值,也可以匹配不同类型的值。

4.匹配


$in简述

和$ne操作符相反,$in相当于包含、等于,查询时查找包含于指定字段条件的数据。具体使用方法如下:

1
2
3
4
5
6
Model.find({ age:{ $in: 20}},function(error,docs){
//查询age等于20的所有数据
});
Model.find({ age:{$in:[20,30]}},function(error,docs){
//可以把多个值组织成一个数组
});

$in和$ne除了条件判定不同,用法是不是很相似呀!

5.或者


$or

$or操作符,可以查询多个键值的任意给定值,只要满足其中一个就可返回,用于存在多个条件判定的情况下使用,如下示例:

1
2
3
Model.find({"$or":[{"name":"yaya"},{"age":28}]},function(error,docs){
//查询name为yaya或age为28的全部文档
});

6.存在


$exists

$exists操作符,可用于判断某些关键字段是否存在来进行条件查询。如下示例:

1
2
3
4
5
6
Model.find({name: {$exists: true}},function(error,docs){
//查询所有存在name属性的文档
});
Model.find({telephone: {$exists: false}},function(error,docs){
//查询所有不存在telephone属性的文档
});

7.小结


简单回顾:

  1. $gt(>),$lt(<),$lte(<=),$gte(>=)操作符:针对Number类型的查询具体超强的排除性。

  2. $ne(!=)操作符:相当于不等于、不包含,查询时可根据单个或多个属性进行结果排除。

  3. $in操作符:和$ne操作符用法相同,但意义相反。

  4. $or操作符:可查询多个条件,只要满足其中一个就可返回结果值。

  5. $exists操作符:主要用于判断某些属性是否存在。

Mongoose三.简单查询

1.简介


查询就是返回一个集合中的文档的子集,Mongoose 模型提供了find、findOne、和findById方法用于文档查询。

这里先添加一些测试数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TestModel.create([
{ name:"test1", age:20 },
{ name:"test2", age:30 },
{ name:"test3", age:24 },
{ name:"test4", age:18 },
{ name:"test5", age:60 },
{ name:"test6", age:50, email:"t6@qq.com" },
{ name:"test7", age:40, email:"t7@163.com" },
{ name:"test8", age:27 },
{ name:"test9", age:27, email:"t9@yeah.net" },
{ name:"test10",age:65 }
], function(error,docs) {
if(error) {
console.log(error);
} else {
console.log('save ok');
}
});

2.find过滤查询


了解和掌握它的过滤功能,怎么个过滤法呢,请看如下介绍。

1.属性过滤 find(Conditions,field,callback);

field省略或为Null,则返回所有属性。

1
2
3
4
//返回只包含一个键值name、age的所有记录
Model.find({},{name:1, age:1, _id:0},function(err,docs){
//docs 查询结果集
})

说明:我们只需要把显示的属性设置为大于零的数就可以,当然1是最好理解的,_id是默认返回,如果不要显示加上(“_id”:0),但是,对其他不需要显示的属性且不是_id,如果设置为0的话将会抛异常或查询无果。

3.findOne查询


与find相同,但只返回单个文档,也就说当查询到即一个符合条件的数据时,将停止继续查询,并返回查询结果。

1.单条数据 findOne(Conditions,callback);

1
2
3
4
TestModel.findOne({ age: 27}, function (err, doc){
// 查询符合age等于27的第一条数据
// doc是查询结果
});

findOne方法,只返回第一个符合条件的文档数据。

4.findById


与findOne相同,但它只接收文档的_id作为参数,返回单个文档。

1.单条数据 findById(_id, callback);

1
2
3
TestModel.findById('obj._id', function (err, doc){
//doc 查询结果文档
});

同样是单条数据,findById和findOne还是有些区别的。

5.小结


简单回顾:

  1. find过滤查询 :find查询时我们可以过滤返回结果所显示的属性个数。

  2. findOne查询 :只返回符合条件的首条文档数据。

  3. findById查询:根据文档_id来查询文档。

Mongoose二.增删改查

1.查询


查询分很多种类型,如条件查询,过滤查询等等,先看最基本的find查询。
1.find查询: obj.find(查询条件,callback);

1
2
3
4
5
6
7
8
9
10
Model.find({},function(error,docs){
//若没有向find传递参数,默认的是显示所有文档
});
Model.find({ "age": 28 }, function (error, docs) {
if(error){
console.log("error :" + error);
}else{
console.log(docs); //docs: age为28的所有文档
}
});

2.Model保存


Model提供了一个create方法来对数据进行保存。下面我们来看一下示例:

  1. Model.create(文档数据, callback))
    1
    2
    3
    4
    5
    6
    7
    Model.create({ name:"model_create", age:26}, function(error,doc){
    if(error) {
    console.log(error);
    } else {
    console.log(doc);
    }
    });

3.Entity保存


entity的保存方法。如下示例:

  1. Entity.save(文档数据, callback))
    1
    2
    3
    4
    5
    6
    7
    8
    var Entity = new Model({name:"entity_save",age: 27});
    Entity.save(function(error,doc) {
    if(error) {
    console.log(error);
    } else {
    console.log(doc);
    }
    });

model调用的是create方法,entity调用的是save方法。

4.更新数据


数据更新!

1.示例:obj.update(查询条件,更新对象,callback);

1
2
3
4
5
6
7
8
9
var conditions = {name : 'test_update'};
var update = {$set : { age : 16 }};
TestModel.update(conditions, update, function(error){
if(error) {
console.log(error);
} else {
console.log('Update success!');
}
});

更新后find一下,此时数据已经修改成功了!

5.删除数据


有了数据的保存、更新,就差删除了。

1.示例:obj.remove(查询条件,callback);

1
2
3
4
5
6
7
8
var conditions = { name: 'tom' };
TestModel.remove(conditions, function(error){
if(error) {
console.log(error);
} else {
console.log('Delete success!');
}
});

和update类似吧,有了remove方法我们就可以进行删除操作了。

6.小结


简单回顾:

  1. 查询:find查询返回符合条件一个、多个或者空数组文档结果。

  2. 保存:model调用create方法,entity调用的save方法。

  3. 更新:obj.update(查询条件,更新对象,callback),根据条件更新相关数据。

  4. 删除:obj.remove(查询条件,callback),根据条件删除相关数据。

Mongoose一.基础知识

1.认识Mongoose


什么是Mongoose呢,它于MongoDB又是什么关系呢,它可以用来做什么呢,介绍Mongoose之前,我们先简单了解一下MongoDB。

MongoDB是一个开源的NoSQL数据库,相比MySQL那样的关系型数据库,它更显得轻巧、灵活,非常适合在数据规模很大、事务性不强的场合下使用。同时它也是一个对象数据库,没有表、行等概念,也没有固定的模式和结构,所有的数据以文档的形式存储(文档,就是一个关联数组式的对象,它的内部由属性组成,一个属性对应的值可能是一个数、字符串、日期、数组,甚至是一个嵌套的文档。),数据格式就是JSON。

介绍了MongoDB,我们下面就要认识Mongoose了。

  1. Mongoose是什么?

Mongoose是MongoDB的一个对象模型工具,是基于node-mongodb-native开发的MongoDB nodejs驱动,可以在异步的环境下执行。同时它也是针对MongoDB操作的一个对象模型库,封装了MongoDB对文档的的一些增删改查等常用方法,让NodeJS操作Mongodb数据库变得更加灵活简单。

  1. Mongoose能做什么?

Mongoose,因为封装了对MongoDB对文档操作的常用处理方法,让NodeJS操作Mongodb数据库变得easy、easy、So easy!

2.使用Mongoose


  1. 安装mongoose:

    1
    npm install mongoose
  2. 引用mongoose:

    1
    var mongoose = require("mongoose");
  3. 使用”mongoose”连接数据库:

    1
    var db = mongoose.connect("mongodb://user:pass@localhost:port/database");
  4. 执行下面代码检查默认数据库test,是否可以正常连接成功?

    1
    2
    3
    4
    5
    6
    7
    8
    var mongoose = require("mongoose");
    var db = mongoose.connect("mongodb://127.0.0.1:27017/test");
    db.connection.on("error", function (error) {
    console.log("数据库连接失败:" + error);
    });
    db.connection.on("open", function () {
    console.log("------数据库连接成功!------");
    });

3.了解集合


MongoDB —— 是一个对象数据库,没有表、行等概念,也没有固定的模式和结构,所有的数据以Document(以下简称文档)的形式存储(Document,就是一个关联数组式的对象,它的内部由属性组成,一个属性对应的值可能是一个数、字符串、日期、数组,甚至是一个嵌套的文档。),后面我们会学习如何创建文档并插入内容。

在MongoDB中,多个Document可以组成Collection(以下简称集合),多个集合又可以组成数据库。

我们想要操作MongoDB数据,那就得先要具备上面所说的包含数据的“文档”,文档又是什么意思呢,请看如下介绍。

文档 —— 是MongoDB的核心概念,是键值对的一个有序集,在JavaScript里文档被表示成对象。同时它也是MongoDB中数据的基本单元,非常类似于关系型数据库管理系统中的行,但更具表现力。

集合 —— 由一组文档组成,如果将MongoDB中的一个文档比喻成关系型数据库中的一行,那么一个集合就相当于一张表。

如果我们要通过Mongoose去创建一个“集合”并对其进行增删改查,该怎么实现呢,到这里我们就要先了解Schema(数据属性模型)、Model、Entity了。

4.Schema


Schema —— 一种以文件形式存储的数据库模型骨架,无法直接通往数据库端,也就是说它不具备对数据库的操作能力,仅仅只是数据库模型在程序片段中的一种表现,可以说是数据属性模型(传统意义的表结构),又或着是“集合”的模型骨架。

那如何去定义一个Schema呢,请看示例:

1
2
3
4
5
6
7
var mongoose = require("mongoose");
var TestSchema = new mongoose.Schema({
name : { type:String },//属性name,类型为String
age : { type:Number, default:0 },//属性age,类型为Number,默认为0
time : { type:Date, default:Date.now },
email: { type:String,default:''}
});

基本属性类型有:字符串、日期型、数值型、布尔型(Boolean)、null、数组、内嵌文档等。

5.Model


Model —— 由Schema构造生成的模型,除了Schema定义的数据库骨架以外,还具有数据库操作的行为,类似于管理数据库属性、行为的类。

如何通过Schema来创建Model呢,如下示例:

1
var db = mongoose.connect("mongodb://127.0.0.1:27017/test");

// 创建Model

1
var TestModel = db.model("test1", TestSchema);

test1:数据库中的集合名称,当我们对其添加数据时如果test1已经存在,则会保存到其目录下,如果未存在,则会创建test1集合,然后在保存数据。

拥有了Model,我们也就拥有了操作数据库的金钥匙。

如果你想对某个集合有所作为,那就交给Model模型来处理吧,创建一个Model模型,我们需要指定:1.集合名称,2.集合的Schema结构对象,满足这两个条件,我们就会拥有一个操作数据库的金钥匙。

6.Entity


Entity —— 由Model创建的实体,使用save方法保存数据,Model和Entity都有能影响数据库的操作,但Model比Entity更具操作性。

使用Model创建Entity,如下示例:

1
2
3
4
5
6
7
var TestEntity = new TestModel({
name : "Lenka",
age : 36,
email: "lenka@qq.com"
});
console.log(TestEntity.name); // Lenka
console.log(TestEntity.age); // 36

创建成功之后,Schema属性就变成了Model和Entity的公共属性了。

7.创建集合


下面是关于一些基础数据的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var mongoose = require("mongoose");
var db = mongoose.connect("mongodb://127.0.0.1:27017/test");
var TestSchema = new mongoose.Schema({
name : { type:String },
age : { type:Number, default:0 },
email: { type:String },
time : { type:Date, default:Date.now }
});
var TestModel = db.model("test1", TestSchema );
var TestEntity = new TestModel({
name : "helloworld",
age : 28,
email: "helloworld@qq.com"
});
TestEntity.save(function(error,doc){
if(error){
console.log("error :" + error);
}else{
console.log(doc);
}
});

上面代码通过request.url属性,判断请求的网址,从而返回不同的内容。

8.小结


学习了如何通过Mongoose去创建一个数据库“集合”,还有定义“集合”的基本组成结构并使其具有相应的操作数据库能力。

简单回顾:

  1. Schema:数据库集合的模型骨架,或者是数据属性模型传统意义的表结构。

  2. Model :通过Schema构造而成,除了具有Schema定义的数据库骨架以外,还可以具体的操作数据库。

  3. Entity:通过Model创建的实体,它也可以操作数据库。

异步—三.async

async


async是一个流程控制库,它就像黑夜中的明灯照亮那陷入callback嵌套泥潭的人们。 这么说虽然有些夸张,但是async确实为我们带来了丰富的嵌套解决方案。

项目地址:

https://github.com/caolan/async

npm 安装

1
npm install async

使用方法:

1
var async = require('async');

2.serires(tasks, callback)


首先登场的是series函数,它的作用是串行执行,一个函数数组中的每个函数,每一个函数执行完成之后才能执行下一个函数,示例如下:

1
2
3
4
5
6
7
8
9
async.series({
one: function(callback){
callback(null, 1);
},
two: function(callback){
callback(null, 2);
}
},function(err, results) {
});

series函数的第一个参数可以是一个数组也可以是一个JSON对象,参数类型不同,影响的是返回数据的格式,如示例中的参数为数组,返回的results应该是这样的’[1,2]’。

3.waterfall(tasks,[callback])


waterfall和series函数有很多相似之处,都是按顺序依次执行一组函数,不同之处是waterfall每个函数产生的值,都将传给下一个函数,而series则没有这个功能,示例如下:

1
2
3
4
5
6
7
8
9
10
11
async.waterfall([  
function(callback){
//task1
callback(null,1);
},function(data,callback){
//task2
callback(null,2);
}
],function(err,results){
console.log(results);
});

另外需要注意的是waterfall的tasks参数只能是数组类型。

4.parallel(tasks,[callback])


parallel函数是并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。 传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序,示例如下:

1
2
3
4
5
6
7
8
9
10
async.parallel([
function(callback){
callback(null, 'one');
},
function(callback){
callback(null, 'two');
}
],
function(err, results){
});

tasks参数可以是一个数组或是json对象,和series函数一样,tasks参数类型不同,返回的results格式会不一样。

5.paralleLimit(tasks,limit,[callback])


parallelLimit函数和parallel类似,但是它多了一个参数limit。 limit参数限制任务只能同时并发一定数量,而不是无限制并发,示例如下:

1
2
3
4
5
6
7
8
9
10
11
async.parallelLimit([
function(callback){
callback(null, 'one');
},
function(callback){
callback(null, 'two');
}
],
2,
function(err, results){
});

6.whilst(test,fn,callback)


相当于while,但其中的异步调用将在完成后才会进行下一次循环。当你需要循环异步的操作的时候,它可以帮助你。示例如下:

1
2
3
4
5
6
7
8
9
10
var count = 0;
async.whilst(
function () { return count < 5; },
function (callback) {
count++;
setTimeout(callback, 1000);
},
function (err) {
}
);

test参数是一个返回布尔值结果的函数,通过返回值来决定循环是否继续,作用等同于while循环停止的条件。

fn参数就是我们要异步执行的作业,每次fn执行完毕后才会进入下一次循环。

7.doWhilst


相当于do…while,较whilst而言,doWhilst交换了fn,test的参数位置,先执行一次循环,再做test判断。

1
2
3
4
5
6
7
8
9
10
var count = 0;
async.doWhilst(
function (callback) {
count++;
setTimeout(callback, 1000);
},
function () { return count < 5; },
function (err) {
}
);

8.until(test,fn,callback)


until与whilst正好相反,当test条件函数返回值为false时继续循环,与true时跳出。其它特性一致。示例如下:

1
2
3
4
5
6
7
8
9
10
var count = 5;
async.until(
function () { return count < 0; },
function (callback) {
count--;
setTimeout(callback, 1000);
},
function (err) {
}
);

9.doUntil(fn,test,callback)


doUntil与doWhilst正好相反,当test为false时循环,与true时跳出。其它特性一致。

示例:

1
2
3
4
5
6
7
8
9
10
var count = 5;
async.doUntil(
function (callback) {
count--;
setTimeout(callback, 1000);
},
function () { return count < 0; },
function (err) {
}
);

10.forever(fn,errback)


forever函数比较特殊,它的功能是无论条件如何,函数都一直循环执行,只有出现程序执行的过程中出现错误时循环才会停止,callback才会被调用。

示例:

1
2
3
4
5
6
async.forever(
function(next) {
},
function(err) {
}
);

11.compose(fn1,fn2…)


使用compose可以创建一个异步函数的集合函数,将传入的多个异步函数包含在其中,当我们执行这个集合函数时,会依次执行每一个异步函数,每个函数会消费上一次函数的返回值。

我们可以使用compose把异步函数f、g、h,组合成f(g(h()))的形式,通过callback得到返回值,请看示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
unction fn1(n, callback) {
setTimeout(function () {
callback(null, n + 1);
}, 1000);
}
function fn2(n, callback) {
setTimeout(function () {
callback(null, n * 3);
}, 1000);
}
var demo = async.compose(fn2, fn1);
demo(4, function (err, result) {
});

12.auto(tasks,[callback])


用来处理有依赖关系的多个任务的执行。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async.auto({
getData: function(callback){
callback(null, 'data', 'converted to array');
},
makeFolder: function(callback){
callback(null, 'folder');
},
writeFile: ['getData', 'makeFolder', function(callback, results){
callback(null, 'filename');
}],
emailLink: ['writeFile', function(callback, results){
callback(null, {'file':results.writeFile, 'email':'user@example.com'});
}]
}, function(err, results) {
console.log('err = ', err);
console.log('results = ', results);
});

示例中writeFile依赖getData和makeFolder,emailLink依赖writeFile。

13.queue(worker,concurrency)


queue相当于一个加强版的parallel,主要是限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用。

它有多个点可供回调,如无等候任务时(empty)、全部执行完时(drain)等。

示例:定义一个queue,其worker数量为2:

1
2
3
4
5
6
7
8
9
10
var q = async.queue(function(task, callback) {
console.log('worker is processing task: ', task.name);
callback();
}, 2);
q.push({name: 'foo'}, function (err) {
console.log('finished processing foo');
});
q.push({name: 'bar'}, function (err) {
console.log('finished processing bar');
});

当最后一个任务交给worker执行时,会调用empty函数:

1
2
3
q.empty = function() {
console.log('no more tasks wating');
}

14.apply(function,arguments…)


apply是一个非常好用的函数,可以让我们给一个函数预绑定多个参数并生成一个可直接调用的新函数,简化代码。示例如下:

1
2
3
function(callback) { 
test(3, callback);
};

用apply改写:

1
async.apply(test, 3);

15.iterator(tasks)


将一组函数包装成为一个iterator,可通过next()得到以下一个函数为起点的新的iterator。该函数通常由async在内部使用,但如果需要时,也可在我们的代码中使用它。

1
2
3
4
5
6
var iter = async.iterator([
function() { console.log('111') },
function() { console.log('222') },
function() { console.log('333') }
]);
iter();

直接调用(),会执行当前函数,并返回一个由下个函数为起点的新的iterator。调用next(),不会执行当前函数,直接返回由下个函数为起点的新iterator。

对于同一个iterator,多次调用next(),不会影响自己。如果只剩下一个元素,调用next()会返回null。

16.小结


async模块在流程控制方面给我们带来了比较全面的解决办法,下面我们来回顾一下都有哪几种方案:

串行控制: series、waterfall、compose;

并行控制:

parallel、parallelLimit、queue;

循环控制:
whilst、doWhilst、until、doUntil、forever;

其他控制:
apply、applyEach、iterator、auto;

异步—二.函数式编程

1.高阶函数


高阶函数是异步编程的基础,那么什么是高阶函数呢?

高阶二字听起来有点高大上的感觉,其实不然,高阶函数与普通函数不同的地方是高阶函数可以把函数作为参数,或者是将函数作为返回值,请看如下示例。

示例:

1
2
3
4
5
function test(v){
return function(){
return v;
}
}

示例中是一个最简单的高阶函数,如你所见,高阶函数test的返回值是一个匿名函数。

2.现实应用


虽然有可能是第一次真正的去了解什么是高阶函数,但是其实我们在日常开发中经常会用到它,只是我们没有去留意或者说不知道它的称谓而已。

示例:数组的排序(sort)函数

1
2
3
4
var arr = [23,54,3,12,78];
arr.sort(function(a,b){
return a-b;
});

有没有很熟悉的感觉,想一想常见的还有哪些高阶函数。

3.偏函数


什么是偏函数?

假设有一个参数或变量已经预置的函数A,我们通过调用A来产生一个新的函数B,函数B就是我们说的偏函数,有点拗口?请看示例:

1
2
3
4
5
6
7
var isType = function(type){
return function(obj){
return toString.call(obj)=='[object '+type+']';
}
};
var isString = isType('String');
var isFunction = isType('Function');

isType函数中预置了判断类型的方法,只指定部分参数来产生的新的定制的函数isString和isFunction就是偏函数。

4.小结


高阶函数和偏函数是异步编程的基础,熟练运用高阶函数和偏函数是非常必要的。

高阶函数

1.函数作为参数;
2.函数作为返回值;
偏函数

1.一个创建函数的工厂函数;
2.通过指定部分参数,定制新的函数;

异步—一.简介

1.简介


异步编程是指由于异步I/O等因素,无法同步获得执行结果时,在回调函数中进行下一步操作的代码编写风格,常见的如setTimeout函数、ajax请求等等。

示例:

1
2
3
4
5
console.log('hi!');
setTimeout(function(){
console.log('hello!');
},1000);
console.log('wow!');

从示例中可以看到,hello是在wow输出后才输出的,因为setTimeout函数设置了延迟1000毫秒才异步执行,

1
function(){ console.log('hello'); }

就是异步回调函数,这样的编程风格就是异步编程。

2.优势


为什么选择node.js?它有哪些优势呢?

1.性能:相对于多线程,异步I/O没有了线程间的上下文切换开销,由此带来可观的性能提升是选择它的主要原因。

2.成本:由于性能的提升,相同的硬件可以发挥更大的作用,变相的降低了运营成本,由于node.js采用javascript作为开发语言,而javascript的使用已经非常广泛,所以降低了node.js的学习成本。

3.效率:node.js采用javascript作为开发语言,使前后端开发语言统一,不需要切换开发语言,使开发效率更高,加之javascript使用者众多,使得node.js迅速的流行起来。

3.思维习惯


不符合线性思维习惯
虽然异步会带来很多好处,但是也衍生了很多问题。 异步编程在流程控制中业务表达不太适合自然语言的线性思维习惯。

实例 - 获取数据
线性表达

1
var data = getData(id);

异步编程:

1
2
3
getData(id,function(data){
//在回调函数中才能获取到data数据
});

4.异常捕获


异步I/O的实现主要有两个阶段,①提交请求;②处理结果; 这两个阶段彼此不关联,而异常并不一定发生在请求提交(即调用函数)时,平常的try/catch并不能有效的捕捉到程序的异常。

示例1:

1
2
3
4
5
6
7
try{
setTimeout(function(){
var data = a/1; //错误的计算
},1000);
}catch (e){
console.log(e);
}

因为计算并不会马上执行,所以即便是发生了错误,也无法捕获到相关信息,那么异步编程中应该如何处理异常的呢?请看示例2:

1
2
3
4
5
6
7
setTimeout(function(){
try{
var data = a/1; //错误的计算
}catch(e){
console.log(e);
}
},1000);

5.函数嵌套


os模块可提供操作系统的一些基本信息,它的一些常用方法如下:
函数嵌套
刚刚接触node.js的朋友们都有一个共同的烦恼,在进行较复杂的业务处理时,茫茫多的callback看得人头昏眼花,写着写着自己都糊涂了,更何况普遍患有强迫症的程序猿,面对如此难看的代码,如何能忍?

1
2
3
4
5
6
7
8
9
test1(function(v1){
test2(funciton(v1,function(v2){
test3(function(v2,fcuntion(v3){
test4(v3,function(v4){
callback(v4);
});
}));
}));
});

异步编程中,函数嵌套是一个普遍存在的问题,也因此常常被人诟病,如何解决函数嵌套,且看下回分解。

6.小结


非线性的思维只是因为和以前的习惯不太一样,才会觉得不太适应,但是长期使用锻炼你也会发现它并非一无是处。

异常捕获在node.js中也有了统一的约定,将异常信息作为回调函数的第一个实参传递给回调函数。 深层嵌套的问题也有很多解决办法。

思维方式 ===> 非线性思维需要多使用锻炼,适应这种思维方式。
异常捕获 ===> 遵守node.js统一的回调函数格式,将异常信息传入回调函数。
函数嵌套 ===> 相应解决方案,专门课程讲解。

入门—八.子进程

1.简介


child_process模块的基本介绍

众所周知node.js是基于单线程模型架构,这样的设计可以带来高效的CPU利用率,但是无法却利用多个核心的CPU,为了解决这个问题,node.js提供了child_process模块,通过多进程来实现对多核CPU的利用. child_process模块提供了四个创建子进程的函数,分别是spawn,exec,execFile和fork。

2.创建子进程

spawn函数的基本用法

spawn函数用给定的命令发布一个子进程,只能运行指定的程序,参数需要在列表中给出。如下示例:

1
2
3
4
5
var child_process = require('child_process');
var child = child_process.spawn( command );
child.stdout.on('data', function(data) {
console.log(data);
});

通过执行命令得到返回结果,我们就可以拿到标准输出流数据了。

3.exec


exec函数的简单用法

exec也是一个创建子进程的函数,与spawn函数不同它可以直接接受一个回调函数作为参数,回调函数有三个参数,分别是err, stdout , stderr,基本使用方法如下:

1
2
3
4
var child_process = require('child_process');
child_process.exec( command , function(err, stdout , stderr ) {
console.log( stdout );
});

4.execFile


execFile函数的简单用法

execFile函数与exec函数类似,但execFile函数更显得精简,因为它可以直接执行所指定的文件,基本使用方法如下:

1
2
3
4
var child_process = require('child_process');
child_process.execFile( file , function(err, stdout , stderr ) {
console.log( stdout );
});

execFile与spawn的参数相似,也需要分别指定执行的命令和参数,但可以接受一个回调函数,与exec的回调函数相同。

5.fork


fork函数的简单用法

fork函数可直接运行Node.js模块,所以我们可以直接通过指定模块路径而直接进行操作。使用方法如下:

1
2
var child_process = require('child_process');
child_process.fork( modulePath );

该方法是spawn()的特殊情景,用于派生Node进程。除了普通ChildProcess实例所具有的所有方法,所返回的对象还具有内建的通讯通道。