1. 回调:通常用来处理一次性响应事件。(如指定一个回调函数来确定如何处理数据库查询结果)
2. 事件监听:通常用来响应重复性事件。本质上也是回调,只不过他是与一个概念实体(事件)相关联。(如HTTP服务器发出请求,进行请求监听,添加一些响应逻辑)。
回调
回调是一个函数,它作为参数传给异步函数,描述了异步操作完成后要做什么。
- ex1:简单服务器
- 从titles.json文件中获取文章的标题
- 从template.html文件中获取HTML模板
- 将这些标题组装到HTML页面中
- 将HTML页面发送给用户
titles.json
{
"Mike",
"Jay",
"Kim"
}
template.html
Boys
- %
server1,js
var http = require("http");
var fs = require("fs");
http.createServer(function(req, res){
if(req.url == '/'){
fs.readFile('./titles.json', function(err, data){ //读取标题
if(err)
{
console.log(err);
res.end("server error");
}else{
var titles = JSON.parse(data.toString()); //得到标题的数组
fs.readFile('./template.html', function(err, data) { //读取模板
if(err){
console.log(err);
res.end("server error");
}else{
var tmpl = data.toString();
var html = tmpl.replace('%', titles.join('')); //组装标题
res.writeHead(200, {'Content-type': 'text/html'}); //响应头部
res.end(html);
}
})
}
});
}
}).listen(8000, "127.0.0.1");
问题:三层嵌套
http.createServer(function(req, res){
fs.readFile('./titles.json', function(err, data){
fs.readFile('./template.html', function(err, data) {
...
解决:创建中间函数
server2.js
var http = require("http");
var fs = require("fs");
http.createServer(function(req, res){
if(req.url == '/'){
getTitles(res);
}
}).listen(8000, "127.0.0.1");
// 获取标题
function getTitle(res) {
fs.readFile("./titles.json", function(err, data) {
if(err){
hadError(err, res);
}else{
getTemplate(JSON.parse(data.toString()), res);
}
});
}
// 获取模板
function getTemplate(titles, res) {
fs.readFile("./template.html", function(err, data) {
if(err){
hadError(err, res);
}else{
formatHtml(titles, data.toString(), res);
}
});
}
// 组装标题
function formatHtml(titles, tmpl, res) {
var html = tmpl.replace("%", titles.join(""));
res.writeHead(200, {"Content-type": "text/html"});;
res.end(html);
}
//处理错误
function hadError(err, res) {
console.log(err);
res.end("server error");
}
进一步解决:通过尽快返回较少嵌套
server3.js
var http = require("http");
var fs = require("fs");
http.createServer(function(req, res){
if(req.url == '/'){
getTitles(res);
}
}).listen(8000, "127.0.0.1");
// 获取标题 return
function getTitle(res) {
fs.readFile("./titles.json", function(err, data) {
if(err){
return hadError(err, res);
}else{
getTemplate(JSON.parse(data.toString()), res);
}
});
}
// 获取模板 return
function getTemplate(titles, res) {
fs.readFile("./template.html", function(err, data) {
if(err){
return hadError(err, res);
}else{
formatHtml(titles, data.toString(), res);
}
});
}
// 组装标题
function formatHtml(titles, tmpl, res) {
var html = tmpl.replace("%", titles.join(""));
res.writeHead(200, {"Content-type": "text/html"});;
res.end(html);
}
//处理错误
function hadError(err, res) {
console.log(err);
res.end("server error");
}
事件监听
事件发射器:会触发事件并在那些事件触发时能处理他们。(如HTTP服务器、TCP服务器、流...)
- 用on响应事件
var net = require("net");
var server = net.createServer(function(socket) {
socket.on("data", function(data) {
socket.write(data);
});
});
server.listen(8888);
- 用once响应一次性事件
var net = require("net");
var server = net.createServer(function(socket) {
socket.once("data", function(data) { //data事件只被处理一次
socket.write(data);
});
});
server.listen(8888);
3.创建事件发射器(EventEmitter):
var EventEmitter = require("events").EventEmitter;
var channel = new EventEmitter(); //事件发射器
channel.on("join", function() { //添加监听器
console.log("welcome");
});
channel.emit("join"); //触发事件
简单的发布/预订系统(PUB/SUB)
var events = require("events");
var net = require("net");
var channel = new events.EventEmitter();
channel.clients = {};
channel.subscriptions = {};
//添加一个新增用户的监听器
channel.on("join",function(id, client) {
this.clients[id] = client; //将新用户加入用户表中
this.subscriptions[id] = function(senderId, message) { //每个用户可以向除了自己之外的用户发送信息
if(id != senderId){
this.clients[id].write(message);
}
}
this.on('broadcast',this.subscriptions[id]); //为每一个用户添加一个发送广播信息的监听器
});
//添加用户断开连接的监听器
channel.on("leave",function(id) {
channel.remoteListener('broadcast',this.subscriptions[id]);
channel.emit("broadcast", id, id + " has left this chat.\n");
});
//定义服务器
var server = net.createServer(function(client) {
var id = client.remoteAddress + ':' + client.remotePort; //将新用户id定义为该用户"ip:端口号"
client.emit("join",client); //触发新增用户事件
client.on("data",function(data) { //当有用户发送消息时,触发broadcast事件,指明用户的id和消息
data = data.toString();
channel.emit('broadcast', id, data);
});
client.on("close",function() {
channel.emit("leave",id);
});
});
server.listen(8888);
异步开发难题
- ex2
function foo(cb){
settimeout(cb, 200);
}
var color = "green";
foo(function(){
console.log(color);
});
color = "red";
结果:red
原因:ex2是异步执行的例子,在console.log执行之前,color的值从green变更为red,200ms后color的值为red。
解决:利用闭包函数保留全局变量的值
- ex3
function foo(cb){
settimeout(cb, 200);
}
var color = "green";
(function(color){
foo(function(){
console.log(color);
})
})(color);
color = "red";
结果:green
原因:对foo函数的调用封装到了一个匿名函数里面,这个匿名函数会马上执行,同时把当前的color的值(green)传给他。color变成了匿名函数的参数,即这个匿名函数内部的本地变量,不受外部全局变量的影响。
异步逻辑的顺序化——流程控制
- 串行流程控制
参考上文server2.js - 并行流程控制
- ex4 并行计算文档文件夹text 里面所有文档的单词出现的次数
var fs = require("fs");
var completeTaskes = 0;
var tasks = [];
var wordCounts = {};
var filesDir = "./text";
function checkIfComplete() {
completeTaskes++;
if(completeTaskes == tasks.length){
for(var index in wordCounts){
console.log(index + ": " + wordCounts[index]);
}
}
}
function countWordInText(text) {
var words = text.toString().toLowerCase().split(/\w+/).sort();
for(var index in words){
wordCounts[word] = (wordCounts[word]) ? wordCounts[word] + 1 : 1;
}
}
fs.readDir(filesDir, function(err, files) {
if (err) throw err;
for(var index in files){
var task = (function(file) {
return function() {
fs.readFile(file, function(err, text) {
if(err) throw err;
countWordInText(text);
checkIfComplete();
})
}
})(filesDir + "/" + files[index]);
tasks.push(task);
}
for(var task in tasks){
tasks[task]();
}
});