NodeJS——异步编程

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

回调

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

  • ex1:简单服务器
  1. 从titles.json文件中获取文章的标题
  2. 从template.html文件中获取HTML模板
  3. 将这些标题组装到HTML页面中
  4. 将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服务器、流...)

    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]();
        }
    });
    

    你可能感兴趣的:(NodeJS——异步编程)