NodeJS——异步编程

1. 回调:通常用来处理一次性响应事件。(如指定一个回调函数来确定如何处理数据库查询结果)
2. 事件监听:通常用来响应重复性事件。本质上也是回调,只不过他是与一个概念实体(事件)相关联。(如HTTP服务器发出请求,进行请求监听,添加一些响应逻辑)。

回调

回调是一个函数,它作为参数传给异步函数,描述了异步操作完成后要做什么。

  • ex1:简单服务器
  1. 从titles.json文件中获取文章的标题
  2. 从template.html文件中获取HTML模板
  3. 将这些标题组装到HTML页面中
  4. 将HTML页面发送给用户

titles.json

{
  "Mike",
  "Jay",
  "Kim"
}

template.html

<html>
<head></head>
<body>
  <h1>Boys</h1>
  <ul>
     <li>%</li>
  </ul>
</body>
</html>

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('</li><li>'));  //组装标题
                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("</li><li>"));
    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("</li><li>"));
    res.writeHead(200, {"Content-type": "text/html"});;
    res.end(html);
}

//处理错误
function hadError(err, res) {
    console.log(err);
    res.end("server error");
}

事件监听

事件发射器:会触发事件并在那些事件触发时能处理他们。(如HTTP服务器、TCP服务器、流...)

  1. 用on响应事件
var net = require("net");
var server = net.createServer(function(socket) {
   socket.on("data", function(data) {
       socket.write(data);
   });
});
server.listen(8888);
  1. 用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变成了匿名函数的参数,即这个匿名函数内部的本地变量,不受外部全局变量的影响。

异步逻辑的顺序化——流程控制

  1. 串行流程控制
    参考上文server2.js
  2. 并行流程控制
  • 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]();
    }
});
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容