[NodeJS]Node异步编程基础

零、前言

  为什么要用Node?

  Node把非阻塞IO作为提高应用性能的方式。而在JS中,天生拥有着异步编程机制: 事件机制。同时JS中不存在多进程。这样当你执行相对较慢需要花费时间长的IO操作时并不会阻塞主进程的任务。

  在NodeJS中流行两种响应逻辑的管理方式: 回调, 事件监听。

回调通常用来定义一次性响应的逻辑。事件监听器本质上也是一个回调,不同的是它跟事件相互关联。

一、使用回调来处理一次性事件

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

案例: 创建一个简单的http服务器实现如下功能

1. 异步获取存放在JSON文件中的文章标题

2. 异步获取简单的HTML模版

3. 将文章标题组装到HTML页面中

4. 将HTML页面发送给用户

var http = require("http");
var fs = require("fs");
var srcFilename = "./titles.json";
var distFilename = "./index.html";

http.createServer(function(req, res){
    if(req.url == ‘/‘){
        fs.readFile(srcFilename, function(err, data){
            if(err){
                console.log(err);
                res.end("server end");
            }else{
                var titles = JSON.parse(data);
                fs.readFile(distFilename, "utf-8", function(err, data){
                    if(err){
                        console.log(err);
                        res.end("server end");
                    }else{
                        var html = data.replace("%", titles.join("</li><li>"));
                        res.writeHead(200, {"Content-type":"text/html"});
                        res.write(html);
                        res.end();
                    }
                });
            }
        });
    }
}).listen(8080, "127.0.0.1");

以上是NodeJS主程序,通过http模块创建一个简单的HTTP服务器。监听指定端口8080和127.0.0.1主机地址。另外通过判断request.url请求路径来使用fs读取不同文件中的内容,最后将这些内容填充到相应文本内容中,通过response响应对象将数据发送给用户。json数据源和html模板文件如下:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta id="viewport" name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
        <title>Node课程</title>
    </head>
    <body>
        <h1>最新Node课程内容:</h1>
        <ul>
            <li>%</li>
        </ul>
    </body>
</html>
[
    "NodeJS模块原理讲解",
    "NodeJS常用模块简单讲解",
    "NodeJS异步编程基础"
]

二、使用事件发射器处理重复性事件

通过给事件绑定一个触发时的回调函数,那么当事件发射器触发事件时,会在事件触发时执行这些回调函数。NodeJS中很多内置核心组件都是事件发射器的子类。如HTTP服务器,Net服务器和Stream流对象等。

简单案例

var net = require("net");

var server = net.createServer(function(socket){
    socket.on("data", function(data){
        socket.write("输入的内容是:");
        socket.write(data);
    });
});
server.listen(8888, "127.0.0.1");

监听器可以针对某些特殊事件的监听只调用一次事件处理的回调函数。使用 .once() 方式

接下来我们就来创建一个属于自己的事件发射器。

首先我们先回顾一下events核心模块 具体可以参考这篇文章: Events核心模块讲解

接下来创建一个简单的发布订阅系统,实现以下功能:

1. 用户连接服务后,可以看到当前正处于连接状态的其他用户

2. 用户连接服务后,可以给所有用户发送消息

3. 用户断开服务后,系统会将该用户从连接用户池中移除

4. 处于某种原因需要暂停服务时,可以通过指定的命令停止服务

var events = require("events");
var net = require("net");

// 创建一个频道发射器,用来管理所有的用户,用户的行为事件及其响应
var channel = new events.EventEmitter();
channel.clients = {};
channel.subscriptions = {};
channel.setMaxListeners(50);

// 注册用户连接服务的事件
channel.on(‘join‘, function(id, client) {
    channel.clients[id] = client;
    channel.subscriptions[id] = function(senderId, message){
        if(id != senderId){
            channel.clients[id].write(message);
        }
    };
    channel.on(‘broadcast‘, channel.subscriptions[id]);
    // 连接服务后先友好地提示当前房间内的人数
    var welcome = "Welcome! Guests online: " + this.listeners("broadcast").length;
    client.write(welcome);
});

// 注册用户断开服务的事件
channel.on(‘leave‘, function(id) {
    // 移除该用户的广播消息事件响应
    channel.removeListener("broadcast", channel.subscriptions[id]);
    // 将消息广播给其他用户
    channel.emit("broadcast", id, id + " has left the chat.\n");
});

// 注册暂停服务的事件
channel.on(‘shutdown‘, function() {
    // 先发消息提醒所有用户,服务已经暂停
    channel.emit("broadcast", ‘‘, ‘Chat has shut down.\n‘);
    // 移除所有的广播事件
    channel.removeAllListener("broadcast");
});

net.createServer(function(socket){
    var id = socket.remoteAddress + " : " + socket.remotePort;
    // 触发用户连接服务的事件
    channel.emit("join", id, socket);
    // 注册用户发送消息的事件
    socket.on(‘data‘, function(data){
        var data = data.toString();
        // 这里先简单设置为暂停服务的指令为shutdown
        if(data == "shutdown"){
            channel.emit("shutdown");
        }else{
            channel.emit("broadcast", id, data.toString());
        }
    });
    // 注册用户离开服务的事件
    socket.on(‘close‘, function() {
        channel.emit("leave", id);
    });
}).listen(8080, ‘127.0.0.1‘);

** 看了前面的两个事件发射器案例,你还可以利用事件发射器来创建一个文件监听器。

通常的做法都是创建一个JS类,通过继承EventEmitter类来处理文件目录下的所有文件。通过监视目录中的文件变化从而将变化的文件进行处理。

var events = require("events");
var util = require("util");
var fs = require("fs");

function Watcher(watcherDir, processedDir){
    this.watcherDir = watcherDir;
    this.processedDir = processedDir;
}
util.inherit(Watcher, events.EventEmitter);

Watcher.prototype.watch = function(){
    var watcher = this;
    fs.readdir(watcher.watcherDir, function(err, files){
        if(err){
            throw err;
        }else{
            for(var index in files){
                if(files[index].isFile()){
                    watcher.emit("process", files[index]);
                }
            }
        }
    });
};

Watcher.prototype.start = function(){
    var watcher = this;
    fs.watchFile(watcherDir, function(){
        watcher.watch();
    });
};

// 创建这样一个文件监听器实例
var watchDir = "./watch";
var processedDir = "./done";
var watcher = new Watcher(watcherDir, processedDir);
// 注册文件处理函数
watcher.on(‘process‘, function(file) {
    var watcherFile = this.watcherDir + "/" + file;
    var processedFile = this.processedDir + "/" + file.toLowerCase();
    // 通过重命名的方式来移动文件
    fs.rename(watchFile, processedFile, function(err){
        if(err){
            throw err;
        }
    });
});

watcher.start();

三、异步逻辑顺序化

异步编程的代码,回调越多,格式化的代码形状看起来就像格斗游戏中的角色发出的波。很显然这是大家不愿意看到的。

那么如何让异步任务能够顺序执行呢?程序流程控制被分为了两类: 串行和并行。

串行就是任务一个接着一个的执行,执行完前一个才能执行接下来的一个。

串行化流程控制的本质在于如何将多个异步任务按照预期的顺序放入一个数组队列中。这样当一个任务执行结束后会从队列中取出下一个任务依次执行。

并行就是任务不需要一个接着一个来执行,而是可以交叉执行,使得任务看起来就像是同时在执行一样。

var fs = require("fs");
var path = require("path");
// 已经完成的任务数
var completedTasks = 0;
// 待完成的任务数组
var tasks = [];
// 所有单词的统计结果
var wordCounts = {};
// 读取的目录
var filesDir = ‘./text‘;

function checkIfComplete(){
    completedTasks++;
    if(completedTasks == tasks.length){
        for(var word in wordCounts){
            console.log("word = " + word + " ; count = " + wordCounts[word]);
        }
    }
}
function countWordsInText(text){
    var words = text.toString().toLowerCase().split(/\W+/);//.sort();
    console.log(words);
    for(var i in words){
        var word = words[i];
        if(word){
            wordCounts[word] = (wordCounts[word])? (wordCounts[word] + 1) : 1;
        }
    }
}

fs.readdir(filesDir, function(err, fileList){
    if(err){
        throw err;
    }else{
        for(var index in fileList){
            console.log(path.join(filesDir, fileList[index]));
            var task = (function(file){
                return function(){
                    fs.readFile(file, function(err, text){
                        if(err){
                            throw err;
                        }else{
                            countWordsInText(text);
                            checkIfComplete();
                        }
                    });
                };
            })(path.join(filesDir, fileList[index]));
            tasks.push(task);
        }
        for(var i in tasks){
            tasks[i]();
        }
    }
});
时间: 2024-08-08 18:37:49

[NodeJS]Node异步编程基础的相关文章

NodeJS的异步编程风格

NodeJS的异步编程风格 http://www.infoq.com/cn/news/2011/09/nodejs-async-code NodeJS运行环境因其支持Javascript语言和异步编程受到开发社区越来越多的关注.从GitHub上的访问量来看,NodeJS项目的关注度在最近几个月已经超过了Ruby及RoR.作为一个新鲜的平台,开发人员开始尝试去接触并运用于实际工作中,比如LinkedIn.Yammer.GitHub.淘宝等企业已经在生产环境中部署了NodeJS应用.不过,在学习No

node 异步编程

node 异步编程 我了解到的node异步编程可分成: 1.回调函数 2.pub/sub模式(发布/订阅模式) 3.promise 4.generator 5.async await 一.直接回调函数 该方法是最直接常用的异步操作方式,比如在setInterval 和 ajax等会使用到, 存在缺点有: 1.代码不易阅读并且容易出现金字塔嵌套问题; 2.一般只能对应一个回调函数(一对一),使用上有一定的限制; fs.readFile('/etc/passwd', function (err, d

[转载]NodeJS的异步编程风格

NodeJS运行环境因其支持Javascript语言和异步编程受到开发社区越来越多的关注.从GitHub上的访问量来看,NodeJS项目的关注度在最近几个月已经超过了Ruby及RoR.作为一个新鲜的平台,开发人员开始尝试去接触并运用于实际工作中,比如LinkedIn.Yammer.GitHub.淘宝等企业已经在生产环境中部署了NodeJS应用.不过,在学习NodeJS的过程中,从同步编程到异步编程风格的转换是开发人员面临的一个主要问题,我们如何去适应呢?技术社区在讨论这种转变,专家Marc Fa

【Node.js基础篇】(七)Node异步编程之事件发射器

事件发射器是Node里除了回调函数外的另一十分重要的异步编程技术. 在MFC等图形界面编程库中,事件发射器是非常常见的,比如,鼠标点击事件,点击了鼠标后,就会触发鼠标点击后的函数--事件发射器触发事件,并且在事件被触发后处理它们.在Node API组件中,如HTTP服务器.TCP服务器等都被做成了事件发射器,所以掌握事件发射器的编程方法,是非常重要的. 使用on添加监听器 步骤: 声明事件发射器类 创建事件发射器对象 使用on添加事件发射器 使用emit发射事件 //事件发射器类声明 var E

异步编程基础

>>返回<C# 并发编程> 1. 概述 2. 报告进度 3. 等待一组任务完成 4. 异常处理 5. 等待任意一个任务完成 6. 避免上下文延续 7. async void 1. 概述 前面的文章介绍了标识了 async 和 await 的代码,是怎么被线程执行的. >>同步上下文-7.5 异步编程(Async) 下面介绍一些类库和常用的API. 2. 报告进度 使用 IProgress<T> 和 Progress<T> 类型 构造 Progre

node异步编程的集中解决方案

一丶通过递归处理异步回调 var fs = require('fs'); // 要处理的文件列表 var files = ['file1', 'file2', 'file3']; function parseFile () { if (files.length == 0) { return; } var file = files.shift(); fs.readFile(file, function (err, data) { // 这里处理文件数据 parseFile(); // 处理完毕后,

异步编程基础知识

异步在IO密集型场景有优势,应用在爬虫场景就是: 原文地址:https://www.cnblogs.com/jameskane/p/8457030.html

[C#] C#并发编程-异步编程基础-报告进度

1 int ProgressRate = 0; 2 3 private async void btnProgress_Click(object sender, EventArgs e) 4 { 5 ProgressRate = 0; 6 7 var progress = new Progress<int>(); 8 9 //progress的进度改变 10 progress.ProgressChanged += (obj1, obj2) => 11 { 12 if (ProgressRa

javascript 异步编程-setTimeout

javascript的执行引擎是单线程的,正常情况下是同步编程的模式,即是程序按照代码的顺序从上到下依次顺序执行.只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),那么在执行期间任何UI更新都会被阻塞,界面事件处理也会停止响应.导致整个页面卡在这个地方,其他任务无法执行. 特别是在for循环语句里,如果for循环的处理逻辑比较复杂,并且循环次数过多,超过1000次时,javasc