事件发布/订阅模式自身并无同步和异步的问题,但在node中,emit()调用多半是伴随时间循环而异步触发的。
订阅:
emitter.on("event1", function(message){
console.log(message);
});
发布
emitter.emit("event1","I am message")
注意事项:
**雪崩问题:**在高访问量、大并发量的情况下缓存失效的场景,此时大量的请求同时涌入数据库中,数据库无法同时承受如此大的查询请求,进而影响到网站整体的响应速度。
**解决方法:**引入事件队列
var proxy = new events.EventEmitter();
var status = "ready";
var select = function(callback){
proxy.once("selected", callback);
if (status === "ready"){
status = "spending";
db.select("SQL", function(results){
status = "ready";
callback(results);
})
}
}
这里我们利用了once()方法,将所有请求的回调都压入事件队列中,利用其执行一次就会将监听器移除的特点,保证每一次回调只会被执行一次。这样就保证了,对于相同的SQL语句,同一个查询开始到结束的过程永远只有一次。一旦查询结束,得到的结果可以被这些调用者共同使用。
另外,此处可能因为存在监听器躲过而引发警告,需要调用下边的代码移除警告
setMaxListeners(0)
**多异步之间的协作方案:**利用偏函数来处理哨兵变量和第三方函数的关系
var emitter = new events.Emitter();
var done = after(times, render);
// 多对多
emitter.on("done", done);
emitter.on("done", other);
var after = function(times, callback){
var count = 0, results = {};
return function (key, value) {
results[key] = value;
count ++;
if (count === times){
callback(results);
}
}
}
db.query(sql, function(err, data){
emitter.emit("done", "data", data)
});
fs.readFile(template_path, "utf-8", function(err, template){
emitter.emit("done", "template", template);
})
EventProxy的 应用
var ep = new EventProxy();
ep.all("template", "data", function(template, data){
// TODD
});
fs.readFile(template_path, "utf-8", function(err, template){
ep.emit("template", template);
})
db.query(sql, function(err, data){
ep.emit("data", data);
})
注意:all()方法订阅的事件如果没有返回值,则应最后订阅该事件。因为侦听器会按订阅事件的顺序接收返回值。
tail()方法:tail()方法与all()方法的区别在于:all()方法的侦听器在满足条件后只会执行一次,tail()方法的侦听器在满足条件执行一次后,如果组合事件中的某个事件再次触发,侦听器会用最新的数据继续执行。
after()方法:实现事件在执行多少次之后执行侦听器的单一事件组合订阅方式
ep.after("data", 10, function(datas){
// TODD
})
异常处理:EventProxy提供了fail()和done()这两个实例方法来优化异常处理
ep.fail(callback)
等价于
ep.fail(function(err){
callback(err);
});
又等价于
ep.bind('error', function(err){
// 卸掉所有处理函数
ep.unbind();
// 异常回调
callback(err);
});
而done()方法的实现,也可参见如下变换:
ep.done('tp1');
它等价于:
function(err, content){
if(err){
return ep.emit('error', err);
}
ep.emit('tp1', content);
}
当传递参数为回调函数时,需手动调用emit()触发事件
ep.done(function (content){
// TODD
// 无需考虑异常
ep.emit('tp1', content)
})
改进:同时传入事件名称和回调函数
ep.done('tp1', function(content){
// TODD
return content;
})
让Promise支持链式执行,主要通过以下两个步骤
实例代码
new Promise(test).then(function (result1) {
console.log('成功1:' + result1);
}).then(function (result2) {
console.log('成功2:' + result2);
}).catch(function (reason) {
console.log('失败:' + reason);
});
推荐廖雪峰的博客
尾触发与Next:
尾触发目前应用最多的地方是Connect的中间件
var app = connect();
app.use(connect.staticCache());
app.use(connect.static(__dirname + '/public'));
app.use(connect.cookieParser());
app.use(connect.session());
app.use(connect.query());
app.use(connect.bodyParser());
app.use(connect.csrf());
app.listen(3001);
通过use()方法注册好一系列中间件后,监听端口上的请求。中间件利用了尾触发的机制,最简单的中间件如下:
function(req, res, next){
// 中间件
}
每个中间件传递请求对象、相应对象和尾触发函数,通过队列形成一个处理流
async
async提供了series()方法来实现一组任务的串行执行
async.series([
function(callback){
fs.readFile('file1.txt', 'utf-8', callback);
},
function (callback){
fs.readFile('file2.txt', 'utf-8', callback);
}
],
function(err, results){
// results => [file1.txt, file2.txt]
})
等价于
fs.readFile('file1.txt', 'utf-8', function (err, content){
if (err){
return callback(err);
}
fs.readFile('file2.txt', 'utf-8', callback (err, data){
if (err){
return callback(err)
}
callback( null, [content, data]);
});
})
callback()并非由指定者指定。每个callback()执行时会将结果保存起来,然后执行下一个调用。一旦出现异常,就结束调用,并将异常传递给最终回调函数的第一个参数。
异步的并行执行:async的parallel()方法
async.parallel([
function(callback){
fs.readFile('file1.txt', 'utf-8', callback);
},
function (callback){
fs.readFile('file2.txt', 'utf-8', callback);
}
],
function(err, results){
// results => [file1.txt, file2.txt]
})
异步调用的依赖处理:series()适合五一来的异步串行执行,但当前一个结果是后一个调用的输入时,series()方法就无法满足需求了。async提供了waterfall()方法。
async.waterfall([
function(callback){
fs.readFile('file1.txt', 'utf-8', function(err, content){
callback(err, content);
})
},
function (arg1, callback){
// arg1 => file2.txt
fs.readFile(arg1, 'utf-8', function(err, content){
callback(err, content);
})
},
function (arg2, callback){
// arg2 => file3.txt
fs.readFile(arg2, 'utf-8', function(err, content){
callback(err, content);
})
}
],function (err, result){
// result => file4.txt
})
自动依赖处理:async提供了一个强大的方法auto()实现复杂的业务处理
假设业务场景为:
简单映射下上述任务:
{
readConfig: function(){},
connectMongoDB: function(){},
connectRedis: function(){},
compileAsserts: function(){},
uploadAsserts: function(){},
startup: function(){}
}
connectMongoDB和connectRedis依赖readConfig,uploadAsserts依赖compileAsserts,startup依赖所有。
var deps = {
readConfig : function (callback){
// read config file
callback();
},
connectMongoDB: ['readConfig', function(callback){
// connect to mongodb
callback();
}],
connectRedis: ['readConfig', function(callback){
// connect to redis
callback();
}],
compileAsserts: function(callback){
// compile asserts
callback();
},
uploadAsserts: ['compileAsserts', function(callback){
// upload to assert
callback();
}],
startup: ['connectMongoDB', 'connectRedis', 'uploadAsserts', function(callback){
// startup
}]
}
async.auto(deps);
用EventProxy实现
ep.asap('readtheconfig', function(){
// read config file
ep.emit('readConfig');
}).on('readConfig', function(){
// connect to mongodb
ep.emit('connectMongoDB');
}).on('readConfig', function(){
// connect to redis
ep.emit('connectRedis');
}).assp('compiletheasserts', function(){
// compile assert
ep.emit('compileAsserts');
}).on('compileAsserts', function(){
// upload to assert
ep.emit('uploadAsserts');
}).all('connectMongoDB', 'connectRedis', 'compileAsserts', function(){
// startup
});
Step: 比async更轻量,只有一个接口Step
Step(
function readFile1(){
fs.readFile('file1.txt', 'utf-8', this);
},
function readFile2(){
fs.readFile('file2.txt', 'utf-8', this);
},
function done(err, content){
console.log(content);
}
)
用到了this关键字,是Step内部的一个next()方法,将异步调用的结果传递给下一个任务作为参数,并调用执行。
并行任务执行:Step中可以调用this的parallel()方法,告诉Step需要等所有任务完成时才进行下一个任务
Step(
function readFile(){
fs.readFile('file1.txt', 'utf-8', this.parallel());
fs.readFile('file2.txt', 'utf-8', this.parallel());
},
function done(err, content1, content2){
// content1 => file1
// content2 => file2
console.log(arguments);
}
)
使用parallel()时,如果异步方法的结果返回的是多个参数,Step只会取前两个参数。
结果分组:类似parallel()的效果,但在结果传递上不同:返回值以数据形式返回。主要用于遍历某个方法,然后将结果存入数组中。而parallel()更适合同时运行两个异步函数。
Step(
function readDir(){
fs.readdir(__dirname, this);
},
function readFile(err, results){
if (err) throw err;
// 创建组
var group = this.group();
results.forEach(function (filename){
if (/\.js$/.test(filename)){
fs.readFile(__dirname + "/" + filename, 'utf-8', group());
}
});
},
function showAll(err, files){
if (err) thow err;
console.dir(files);
}
)
bagpipe的解决方案
bagpipe的API主要暴露了一个push()方法和pull事件,实例代码如下:
var Bagpipe = require('bagpipe');
// 设定最大并发数为10
for (var i = 0; i < 100; i++){
bagpipe.push(async, function(){
// 异步回调执行
})
}
bagpipe.on('full', function(length){
console.warn("目前队列长度为:", length);
})
拒绝模式:有些场景需要尽快返回数据,快速成功获取数据或者失败,即立即返回数据。这时候可以用bagpipe的拒绝模式。
// 设置最大并发数为10
var bagpipe = new Bagpipe(10, {
refuse: true
})
在拒绝模式下,如果等待的调用队列也满了之后,新来的调用就直接返回拒绝异常
超时限制:为了防止某些异步调用使用太长的时间,我们需要设置一个时间基线,将那些执行时间太久的异步调用清理出活跃队列,让排队中的异步调用尽快执行,否则在拒绝模式下,会有太多调用因为某个执行的慢,导致得到拒绝异常。
// 设置最大并发数
var bagpipe = new Bagpipe(10, {
// 设置超时限制为3s
timeout: 3000
})
-------总结自《深入浅出NodeJS》
ES6中async的用法
async函数返回一个Promise对象,可以使用then方法添加回调函数.当函数执行时,遇到await就会等待其异步操作完成,然后执行函数体后面的语句
1.async放在函数前,表示函数里有异步操作.
如:
async function foo(){} //函数声明
或
const foo = async function foo(){} // 函数表达式
2.async函数的返回值为Promise对象
async function f(){
return 'hello world';
}
f().then(v => console.log(v));
// "hello world"
async function f(){
throw new Error('报错了'); // 返回的Promise对象为reject状态
}
f().then(
v => console.log(v),
e => console.log(e)
);
// Error: 报错了
// 用catch接受错误信息
async function f(){
throw new Error('报错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error: 报错了
3.await表示紧跟在后面的表达式需要等待结果.一般为Promise对象,如果不是,会被转成一个立即resolve的Promise对象
4. 防止异步函数出错,需要把await命令放在try…catch代码块中.和for循环一起用可实现重复尝试某函数.如网络原因可能发送邮件失败,尝试多次发送,捕捉失败原因,一旦成功,break结束循环.
function test2(){
setTimeout(()=>{
console.log(1000);
},1000)
}
async function test(){
for(let i = 0; i < 5; i++){
try {
await test2(); // 异步函数
break
} catch (e) {
console.log('a', e);
}
}
}
test();