函数参数只接受基本数据类型或者对象引用,返回值也是基本数据类型和对象引用。javascript
//常规参数传递和返回
function foo(x) {
return x;
}
复制代码
高阶函数则是能够把函数做为参数和返回值的函数。前端
function foo(x) {
return function() {
return x
}
}
复制代码
function foo(x, bar) {
return bar(x);
}
复制代码
上面这个函数相同的foo函数可是传入的bar参数不一样则能够返回不一样的结果,列如数组的sort()方法,它是一个高阶函数,接受一个参数方法做为排序运算。java
var arr = [40, 80, 60, 5, 30, 77];
arr.sort(function(a, b) {
return a - b;
});
//运行结果[5, 30, 40, 60, 77, 80];
复制代码
经过修改sort方法的参数能够决定不一样的排序方法,这就是高阶函数的灵活性。
结合Node的基本事件模块能够看到,事件处理方式正是基于高阶函数的特性来完成的。在自定义事件中,经过为相同的事件注册不一样的回调函数,能够很灵活的处理业务逻辑。jquery
var emit = new events.EventEmitter();
emit.on('event', function() {
//do something
});
复制代码
偏函数指的是建立一个调用另一部分(参数或者变量已经预置的函数)的函数的用法例如:sql
var toString = Object.prototype.toString;
var isString = function(obj) {
return toString.call(obj) == '[object String]';
}
var isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
}
复制代码
这段代码用于判断类型,一般会进行上述定义,代码存在类似性若是要判断更多会定义更多的isXXX()方法,这样就会出现冗余代码。为了解决重复问题,引入一个新函数用于批量建立这样的相似函数。数据库
var isType = function (type) {
return function(obj) {
return toString.call(obj) == '[object '+ type +']';
}
}
var isString = isType('String');
var isFunction = isType('Function');
复制代码
这种经过指定部分参数来产生一个新的定制函数的形式就是偏函数。编程
Node的最大特性是基于事件驱动的非阻塞I/O模型,这使得CPU和I/O不互相依赖,让资源更好的利用,对于网络应用而言使得各个单点之间能够更有效的组织起来,这使得Node在云计算中广受青睐。 因为事件循环模型要应对海量请求,全部请求做用在单线程上须要防止任何一个计算过多的消耗CPU时间片。建议计算对CPU的耗用不超过10ms,或将大量的计算分解成小量计算,经过setImmediate()进行调度。api
一般处理异常时使用 try/catch/final语句进行异常捕获:数组
try {
JSON.parse(str);
}catch(e) {
console.log(e)
}
复制代码
但这对于异步编程不必定适用。I/O实现异步有两个阶段:提交请求和处理结果。这两个阶段中间有事件循环调度,彼此互不关联,一步方法一般在第一个阶段请求提交后当即返回,可是错误异常并不必定发生在这个阶段,try/catch就不必定会发生做用了。promise
var async = function(callback) {
process.nextTick(callback);
}
try {
async(callback);
}catch(e) {
}
复制代码
调用async方法后callback会被存起来知道下一个事件循环才被执行,try/catch操做只能捕获当前时间循环内的异常。对callback中的异常不起做用。
Node在处理异常上造成了一个约定,将异常做为回调的第一个参数传回,若是是空值,代表没有异常抛出:
async(function(err, res)) {
});
复制代码
在自行编写的异步方法上也须要去遵循这样的原则: 1.必须执行调用者传入的回调函数; 2.正确的传回异常供调用者判断;
var async = function(callback) {
process.nextTick(function() {
var res = 'something';
if(error) {
return callback(error);
}else {
return callback(null, res);
}
})
}
复制代码
在异步编程中,另外一个容易犯的错误是对用户传递的callback进行异常捕获,
try {
req.body = JSON.parse(buf, options.reviver);
callback();
}catch(e) {
err.body = buf;
err.status = 400;
callback(e);
}
复制代码
若是JSON.parse出现错误代码将进入catch部分这样回调函数callback将被执行两次,正确的作法应该是
try {
req.body = JSON.parse(buf, options.reviver);
}catch(e) {
err.body = buf;
err.status = 400;
return callback(e);
}
callback();
复制代码
Node中事物中会出现多个异步调用的场景,列如遍历目录:
fs.readdir('path', function(err, files) {
files.forEach(function(fileName, index) {
fs.readFile(fileName, 'utf8', function(err, file) {
//TODO
})
})
});
复制代码
上述操做因为两次操做存在依赖关系,函数嵌套行为情有可原,可是在某些场景列如渲染网页:一般须要数据、模版、资源文件,这三个操做互不依赖可是最终结果又是三者不可缺一,若是采用默认异步调用会是这样:
fs.readFile('path', 'utf8', function(err, templete) {
db.query(sql, function(err, data) {
l10n.get(function(err, res) {
//TODO
})
})
})
复制代码
这样致使了代码嵌套过深,不易读且很差维护。
javascript没有sleep()这样让线程沉睡的功能,只有setInterval()和setTimeout()这两个函数,可是这两个函数不能阻塞后面的代码执行。
说到javascript时候,一般谈的是单线程上执行。随着业务复杂,对于多核CPU的利用要求也愈来愈高。浏览器中提出了Web Workers。能够更好的利用多核CPU为大量计算服务。前端Web Workers也是利用消息机制合理的使用多核CPU的理想模型。
Node提供了绝大部分的异步API和少许的同步API,Node中试图同步编程,但并不能获得原生支持,须要借助库或者编译手段实现。
目前,异步编程主要解决方案有三种:
Node自身提供的events模块是发布/订阅的一个简单实现,Node中部分模块都继承自它。它具备addListener/on()、 once()、 removeListener()、 removeAllListener()、 emit() 等基础方法。
//订阅
emitter.on('event', function(msg) {
console.log(msg)
});
emitter.emit('event', 'this is msg');
复制代码
Node对事件发布/订阅的机制作了一些额外处理:
实现一个继承EventEmitter的类:
var events = require('events');
function Stream() {
events.EventEmitter.call(this);
}
util.inherits(stream, events.EventEmitter);
复制代码
Node在util模块封装了继承方法。
在事件订阅/发布模式中,一般有一个once()方法,经过它添加的侦听器只能执行一次,执行后将被移除。
在计算机中,缓存因为放在内存中,访问书的快,用于加速数据访问,让绝大多数请求没必要重复去作一些低效的数据读取。所谓的雪崩问题,就是高访问量、大并发量的状况下缓存失效的状况,此时大量的请求同时涌入数据库中,数据库没法承受大量查询请求进而影响网站总体响应速度。
//查询数据库
var select = function(callback) {
db.select(sql, function(err, res) {
callback(res);
})
}
复制代码
若是站点刚启动缓存中尚未数据,若是访问量巨大,同一句sql会被执行屡次反复查询数据库,将会影响性能。改进方案:加一个状态锁
var status = 'ready';
var select = function(callback) {
if(status === 'ready') {
status = 'pending';
db.select(sql, function(err, res) {
status = 'ready';
callback(res);
})
}
}
复制代码
可是在这种状况下连续屡次调用只有第一次调用是生效的,后续调用是没有数据服务的,这个时候能够引入事件列队:
var proxy = new events.EventEmitter();
var status = 'ready';
var select = function(callback) {
proxy.once('selected', callback);
if(status == 'ready') {
status = 'pending';
db.select(sql, function(err, res) {
proxy.emit('selected')
status = 'ready';
})
}
}
复制代码
利用once()方法,将全部请求的回调压入事件列队,利用其执行一次就好将监视器移除的特色,保证一次回调只会被执行一次。
以上面提到的渲染网页(模版读取、数据读取、本地资源读取)为例:
var count = 0;
var res = {};
var done = function(key, val) {
res[key] = val;
count ++;
if(count === 3) {
render(res);
}
};
fs.readFile(template_path, 'utf8', function(err, tp) {
done('tp', tp);
});
db.query(sql, (err, data) {
done('data', data);
});
l10n.get(function(err, res) {
done('res', res);
});
复制代码
一般用于检测次数的变量叫作'哨兵变量'。利用偏函数来处理哨兵变量和第三方函数的关系:
var after = function(times, callback) {
var count = 0,res = {};
return function(key, val) {
res[key] = val;
count ++;
if(count == times) {
callback(res);
}
}
}
var emitter = new events.EventEmitter();
var done = after(times, render);
emitter.on('done', done);
emitter.on('done', other);
fs.readFile(template_path, 'utf8', function(err, tp) {
emitter.emit('done', 'tp', tp);
});
db.query(sql, (err, data) {
emitter.emit('done', 'data', data);
});
l10n.get(function(err, res) {
emitter.emit('done', 'res', res);
});
复制代码
扑灵写的EventProxy模块,是对事件订阅/发布模式的扩充
var proxy = new EventProxy();
proxy.all('tp', 'data', 'res', function(tp, data, res) {
//TODO
});
fs.readFile(template_path, 'utf8', function(err, tp) {
proxy.emit('tp', tp);
});
db.query(sql, (err, data) {
proxy.emit('data', data);
});
l10n.get(function(err, res) {
proxy.emit('res', res);
});
复制代码
EventProxy提供了all()方法来订阅多个事件,全部事件触发后侦听器才会被触发。另外一个tail()方法在知足条件时只需一次后,若是组合事件中的某个事件再次被触发,侦听器会用最新的数据继续只需。
after()方法:实现事件在执行多少次后执行侦听器的单一事件组合订阅方式:
//执行10次data事件后触发侦听器
var proxy = new EventProxy();
proxy.after('data', 10, function(datas) {
//TODO
})
复制代码
EventProxy原理
EventProxy来源自Backbone的事件模块,它在每一个非all的事件触发时都会触发一次all事件
trigger: functuon(eventName) {
var list, calls, ev, callback, args;
var both = 2;
if(!(calls = this._callbacks)) return;
while (both--) {
ev = both?eventName:'all';
if(list = calls[ev]) {
for(var i = 0, l = list.length; i < 1; i ++) {
if(!(callback = list[i])) {
list.splice(i, i);
i --;
l --;
}else {
args = both? Array.prototype.slice.call(arguments, 1):argument;
callback[0].apply(callback[1] || this, args);
}
}
}
}
return this;
}
复制代码
EventProxy则是将all当作一个事件流的拦截层,在其中注入一些业务来处理单一事件没法解决的异步处理问题。
EventProxy提供了fail()和done()两个实例方法来优化异常处理。
var proxy = new EventProxy();
proxy.all('tp', 'data', function(tp, data, res) {
//TODO
});
proxy.fail(function(err) {
//错误处理
})
fs.readFile(template_path, 'utf8', proxy.done('tp'));
db.query(sql, proxy.done('data'));
proxy.done('tp')等价于
function(err, data) {
if(err) {
return proxy.emit('error', err)
}
proxy.emit('tp', data)
}
复制代码
使用事件的方式时,执行的流程被预先设定。Promise/Deferred模式先执行异步调用,延迟传递处理方式。
//普通jquery Ajax调用
$.get('api',{
success: onSuccess,
error: onError,
complete: onComplete
})
复制代码
须要提早预设对应的回调函数
//Promise/Deferred模式 jquery Ajax调用
$.get('api')
.success(onSuccess)
.error(onError)
.complete(onComplete);
复制代码
Promise/Deferred模式即便不传入回调函数也能执行,传统方法一个事件只能传入一个回调函数,而Deferred对象能够对事件加入任意业务处理逻辑。
$.get('api')
.success(onSuccess1)
.success(onSuccess2);
复制代码
CommonJS抽象出了 Promises/A,Promises/B,Promises/D这样的典型异步Promise/Deferred模型。
Promises/A提议对单个异步操做作出这样的定义:
Promises/A API定义比较简单。一个Promise对象只要具有then()方法便可。对应then()的要求:
then()方法定义:
then(fulfilledHandler, errorHandler, progressHandler)
复制代码
Promises/A演示:
var Promise = function() {
EventEmitter.call(this);
};
util.inherit(Promise, EventEmitter);
Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
if(typeof fulfilledHandler === 'function') {
this.once('success', fulfilledHandler)
}
if(typeof errorHandler === 'function') {
this.once('error', errorHandler)
}
if(typeof progressHandler === 'function') {
this.on('progress', progressHandler)
}
return this;
}
复制代码
then()方法将回调函数存放起来,为了完成整改流程,还须要触发执行这些函数的地方,实现这些功能的对象被称为Deferred,即延迟对象:
var Deferred = function() {
this.state = 'unfulfilled';
this.promise = new Promise();
}
Deferred.prototype.resolve = function(obj) {
this.state = 'fulfilled';
this.promise.emit('success', obj);
}
Deferred.prototype.reject = function(obj) {
this.state = 'faild';
this.promise.emit('error', obj);
}
Deferred.prototype.progress = function(obj) {
this.promise.emit('progress', obj);
}
复制代码
res.setEncoding('utf8');
res.on('data', function(chunk) {
//成功
console.log(chunk)
});
res.on('end', function() {
//失败
})
res.on('error', function(err) {
//progress
})
//经过改造
var promisify = function(res) {
var deferred = new Deferred();
var res = '';
res.on('data', function(chunk) {
res += chunk;
deferred.progress(chunk);
})
res.on('end', function() {
promise.resovle(res);
})
res.on('error',function(err) {
promise.reject(err)
})
return deferred.promise;
}
//简便写法
promisify(res).then(function() {
//成功
},function(err) {
//失败
}, function(chunk) {
//progress
})
复制代码
从上面代码能够看出,Deferred主要用于内部,用于维护异步模型的状态;Promise则做用于外部,经过then()方法暴露给外部添加自定义逻辑。
Q模块...
简单原型实现:
Deferred.prototype.all = function(promises) {
var count = promises.length;
var that = this;
var res = [];
promises.forEach(function(promise, i) {
promise.then(function(data) {
count --;
res[i] = data;
if(count === 0) {
that.resolve(res);
}
}, function(err) {
that.reject(err);
})
return this.promise;
})
}
复制代码
经过all()方法抽象多个异步操做。只有全部异步操做成功,这个异步操做才算成功,其中有一个失败,整个异步操做就失败。
(实际使用推荐使用when,Q模块,是对完整的Promise提议的实现)
现有一组纯异步的API,为完成一件串联事代码以下:
obj.api1(function(val1) {
obj.api2(val1, function(val2) {
obj.api3(val2, function(val3) {
obj.api4(val3, function(val4) {
callback(val4);
})
})
})
})
复制代码
使用普通函数将上面代码展开:
var handler1 = function(val1) {
obj.api2(val1, handler2);
}
var handler2 = function(val2) {
obj.api3(val2, handler3);
}
var handler3 = function(val3) {
obj.api4(val3, handler4);
}
var handler41 = function(val4) {
callback(val4)
}
obj.api1(handler1);
复制代码
使用事件机制
var emitter = new EventEmitter();
emitter.on('step1', function() {
obj.api1(function(val1) {
emitter.emit('step2', val1);
})
})
emitter.on('step2', function(val1) {
obj.api2(val1, function(val2) {
emitter.emit('step3', val2);
})
})
emitter.on('step3', function(val2) {
obj.api3(val2, function(val3) {
emitter.emit('step42', val3);
})
})
emitter.on('step4', function(val3) {
obj.api4(val3, function(val4) {
callback(val4);
})
})
emitter.emit('step1');
复制代码
使用事件后代码量明显增长,须要一种更好的方式。
支持序列执行的Promise
理想的方法---链式调用:
promise()
.then(obj.api1)
.then(obj.api2)
.then(obj.api3)
.then(obj.api4)
.then(function(val4) {
},function(err) {
}).done();
复制代码
经过改造代码以实现链式调用:
var Deferred = function() {
this.promise = new Promise();
}
//完成状态
Deferred.prototype.resolve = function(obj) {
var promise = this.promise;
var handler;
while((handler = promise.queue.shift())) {
if(handler && handler.fulfilled) {
var ret = handler.fulfilled(obj);
if(ret && ret.isPromise) {
ret.queue = promise.queue;
this.promise = ret;
return;
}
}
}
}
//失败状态
Deferred.prototype.reject = function(err) {
var promise = this.promise;
var handler;
while((handler = promise.queue.shift())) {
if(handler && handler.error) {
var ret = handler.error(err);
if(ret && ret.isPromise) {
ret.queue = promise.queue;
this.promise = ret;
return;
}
}
}
}
//生成回调函数
Deferred.prototype.callback = function() {
var that = this;
return function(err, file) {
if(err) {
return that.reject(err);
}else {
that.resolve(file);
}
}
}
var Promise = function() {
this.queue = [];
this.isPromise = true;
}
Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
var handler = {};
if(typeof fulfilledHandler === 'function') {
handler.fulfilled = fulfilledHandler;
}
if(typeof errorHandler === 'function') {
handler.errorHandler = errorHandler;
}
this.queue.push(handler);
return this;
}
复制代码
以两次文件读取为例,假设读取第二个文件是依赖第一个文件中的内容:
var readFile1 = function(file, encoding) {
var deferred = new Deferred();
fs.readFile(file, encoding, deferred.callback());
return deferred.promise;
}
var readFile2 = function(file, encoding) {
var deferred = new Deferred();
fs.readFile(file, encoding, deferred.callback());
return deferred.promise;
}
readFile1('file1.txt', 'utf8').then(function(file1) {
return readFile2(file1.trim(), 'utf8');
}).then(function(file2) {
//file2
})
复制代码
要让Promise支持链式执行,主要经过两个步骤。
//smooth(fs.readFile)
var smooth = function(method) {
return function() {
var deferred = new Deferred();
var ags = Array.prototype.slice.call(argument, 1);
args.push(deferred.callback());
method.apply(null, args);
return deferred.promise;
}
}
复制代码
上面的两次读取文件能够简化为:
var readFile = smooth(fs.readFile);
readFile('file1.txt', 'utf8').then(function(file1) {
return readFile(file1.trim(), 'utf8');
}).then(function(file2) {
})
复制代码
书中没有提到这种模式,下面补充一下。
Generator函数是ES6提供的一个异步解决方案。最大的特色是能够暂停执行。 Generator函数和普通的函数区别有两个, 1:function和函数名之间有一个*号, 2:函数体内部使用了yield表达式
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(Iterator Object)须要调用遍历器的next()方法才能使函数继续执行,直到遇到yield方法再次暂停执行。
function* gen() {
var a = 10;
console.log(a);
yield a ++;
console.log(a);
}
复制代码
上面是一个Generator函数,运行:
var g = gen();
复制代码
并么有打印出a的值,执行gen()后只是获得了一个遍历器对象。
g.next();
//10
复制代码
执行遍历器的next()方法后输出了10。
g.next();
//11
复制代码
再次执行next(),yield后的语句被执行输出10。
遇到yield表达式,就暂停执行后面的操做,并将紧跟在yield后面的那个表达式的值,做为返回的对象的value属性值
function *readFileStep(path1) {
let path2 = yield new Promise((resovle, reject) => {
fs.readFile(path1, 'utf8', (err, data) => {
resovle(data);
});
});
let path3 = yield new Promise((resovle, reject) => {
fs.readFile(path2, 'utf8', (err, data) => {
resovle(data);
});
});
return new Promise((resovle, reject) => {
fs.readFile(path3, 'utf8', (err, data) => {
resovle(data);
});
});
}
function run(it) {
function go(result) {
if (result.done) {
return result.value;
}
return result.value.then(function(value) {
return go(it.next(value));
});
}
return go(it.next());
};
run(readFileStep('./file1.txt')).then((data) => {
console.log(data)
});
复制代码
async函数是对Generator函数的改进,在ES7中出现。Generator函数须要依靠执行器才能执行,async函数自带执行器执行方法与常规函数同样。
与Generator函数同样在异步操做的时候async函数返回一个Promise对象,使用then()方法进行后续处理。
使用async实现Generator函数中的样例:
async function readFileStep(path1) {
let path2 = await new Promise((resovle, reject) => {
fs.readFile(path1, 'utf8', (err, data) => {
resovle(data);
});
});
let path3 = await new Promise((resovle, reject) => {
fs.readFile(path2, 'utf8', (err, data) => {
resovle(data);
});
});
return new Promise((resovle, reject) => {
fs.readFile(path3, 'utf8', (err, data) => {
resovle(data);
});
});
}
readFileStep('./file1.txt').then((data) => {
console.log(data)
});
复制代码
只须要像普通函数同样执行readFileStep便可获得最终结果的Promise对象。