NodeJS——异步编程

2018-03-06  本文已影响0人  Eylen

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

回调

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

  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);


异步开发难题

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。
解决:利用闭包函数保留全局变量的值

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. 并行流程控制
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]();
    }
});
上一篇 下一篇

猜你喜欢

热点阅读