为何要用Node?html
Node把非阻塞IO做为提升应用性能的方式。而在JS中,天生拥有着异步编程机制: 事件机制。同时JS中不存在多进程。这样当你执行相对较慢须要花费时间长的IO操做时并不会阻塞主进程的任务。编程
在NodeJS中流行两种响应逻辑的管理方式: 回调, 事件监听。json
回调一般用来定义一次性响应的逻辑。事件监听器本质上也是一个回调,不一样的是它跟事件相互关联。数组
回调是一个函数,被当作参数传递给异步函数,描述了异步操做完成后要作什么。服务器
案例: 建立一个简单的http服务器实现以下功能异步
1. 异步获取存放在JSON文件中的文章标题socket
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](); } } });