您已经看到了许多不同的 Node.js 模块,您可以在希望构建的 Node.js 应用中利用这些模块。但是,在构建 Node.js 应用时,可以使用 Node.js 核心的许多其他模块和部分。这一章将涉及到一些本地 Node.js 模块,并扩展它们的实现,让你对这些模块有更好的理解。理解这些模块在构建 Node.js 应用中起着至关重要的作用是很重要的。
在本章中,您将使用域名系统(DNS)模块来解析远程服务器的主机名和 IP 地址。通过使用缓冲区,您将获得更好的流处理,并且您将看到应用的集群化。您将使用全局流程对象,利用计时器,并处理服务器请求上的查询字符串。您还将看到 Node.js 控制台中公开的内容,以及 Node.js 中可用的调试器 URL。
7-1.使用 DNS 创建简单的 DNS 服务器
问题
您希望能够在 Node.js 应用中从远程服务器获取信息。这些信息可以是 IP 地址或域名。
解决办法
Node.js 为您提供了一种访问远程服务器的域名、IP 地址和域名的方法。这可以通过创建一个接受域名的简单 Node.js 命令行应用(如清单 7-1 所示)来实现。结果是与该域名相关联的所有 IP 地址的列表。
清单 7-1 。DNS 查找命令行工具
/**
* DNS
*/
var dns = require('dns'),
args = process.argv.splice(2),
domain = args[0];
dns.resolve(domain, function (err, addresses) {
if (err) throw err;
addresses.forEach(function (address) {
getDomainsReverse('resolve', address);
});
});
dns.lookup(domain, function(err, address, family) {
if (err) console.log(err);
getDomainsReverse('lookup', address);
});
function getDomainsReverse(type, ipaddress) {
dns.reverse(ipaddress, function(err, domains) {
if (err) {
console.log(err);
} else if (domains.length > 1) {
console.log(type + ' domain names for ' + ipaddress + ' ' + domain);
} else {
console.log(type + ' domain name for ' + ipaddress + ' ' + domain);
}
});
}利用这个命令行工具将产生类似于清单 7-2 中的输出,显示查询的结果以及使用了哪种 Node.js DNS 工具来收集结果。
清单 7-2 。使用 Node.js DNS 查找命令行工具
$ node 7-1-1.js g.co
resolve domain name for 173.194.46.37 g.co
resolve domain name for 173.194.46.38 g.co
resolve domain name for 173.194.46.39 g.co
resolve domain name for 173.194.46.40 g.co
resolve domain name for 173.194.46.46 g.co
resolve domain name for 173.194.46.32 g.co
resolve domain name for 173.194.46.33 g.co
resolve domain name for 173.194.46.34 g.co
resolve domain name for 173.194.46.36 g.co
lookup domain name for 74.125.225.78 g.co
resolve domain name for 173.194.46.41 g.co
resolve domain name for 173.194.46.35 g.co它是如何工作的
Node.js 实现了一个 DNS 版本,它是“C-ares”的包装版本。这是一个为异步 DNS 请求而构建的 C 库。该模块是 Node.js ' dns'模块,是上述解决方案所必需的。
上面的解决方案允许您运行 Node.js 命令,并传递您希望解析或查找的域名。这将从 Node.js 进程的参数中解析出来,传递给查询该域名的两个方法。DNS 对象上的这两个方法是dns.resolve()和dns.lookup()。其中每一个都执行类似的任务,因为它们都将从 DNS 服务器获取与传递给该函数的域名相关联的 IP 地址。然而,实现却大不相同。
dns.lookup()函数将接受一个域名和一个回调。有一个可选的第二个参数,可以向其中传递族参数。family 参数将是 4 或 6,表示您希望查询哪个 IP 系列的地址。对dns.lookup()函数的回调将提供错误、地址和族参数,如果它们可用的话。从下面查找函数的源代码中可以看出,一旦配置了初始参数,Node.js 就会调用 C-ares DNS 模块 cares.getaddrinfo的本机包装器,并返回包装后的结果。
清单 7-3 。来自 Node.js dns.js 源的 Dns.lookup 方法
exports.lookup = function(domain, family, callback) {
// parse arguments
if (arguments.length === 2) {
callback = family;
family = 0;
} else if (!family) {
family = 0;
} else {
family = +family;
if (family !== 4 && family !== 6) {
throw new Error('invalid argument: `family` must be 4 or 6');
}
}
callback = makeAsync(callback);
if (!domain) {
callback(null, null, family === 6 ? 6 : 4);
return {};
}
if (process.platform == 'win32' && domain == 'localhost') {
callback(null, '127.0.0.1', 4);
return {};
}
var matchedFamily = net.isIP(domain);
if (matchedFamily) {
callback(null, domain, matchedFamily);
return {};
}
function onanswer(addresses) {
if (addresses) {
if (family) {
callback(null, addresses[0], family);
} else {
callback(null, addresses[0], addresses[0].indexOf(':') >= 0 ? 6 : 4);
}
} else {
callback(errnoException(process._errno, 'getaddrinfo'));
}
}
var wrap = cares.getaddrinfo(domain, family);
if (!wrap) {
throw errnoException(process._errno, 'getaddrinfo');
}
wrap.oncomplete = onanswer;
callback.immediately = true;
return wrap;
};本解决方案中使用的第二个函数是 dns.resolve()函数,用于解析域名。这个函数也接受一个域和一个回调,并带有可选的第二个参数。回调函数提供了一个错误和一个已经解决的地址数组。如果该方法导致错误,它将是表 7-1 中显示的代码之一。
表 7-1 。DNS 错误代码
| dns 格式的错误。错误) | 描述 |
|---|---|
| ADDRGETNETWORKPARAMS | 找不到 GetNetworkParams 函数 |
| 坏家庭 | 不支持的地址族 |
| BADFLAGS | 指定了非法标志 |
| 错误提示 | 指定了非法的提示标志 |
| 坏名 | 域名格式不正确 |
| 坏查询 | 格式错误的 DNS 查询 |
| 巴德雷普 | 格式错误的 DNS 回复 |
| 巴德斯特勒 | 格式错误的字符串 |
| 取消 | 取消 DNS 查询 |
| 经济复兴 | 无法联系 DNS 服务器 |
| 毁灭 | 频道被破坏了 |
| 文件结束 | 文件结尾 |
| 文件 | 读取文件时出错 |
| 前任的 | DNS 服务器声明查询格式不正确 |
| LOADIPHLPAPI | 加载 iphlpapi.dll 时出错 |
| 无数据 | DNS 服务器返回了一个没有数据的答案 |
| 叫什么名字 | 被遗忘 |
| 无名 | 给定的主机名不是数字 |
| 找不到 | 找不到域名 |
| 白色祛皱美眼笔 | DNS 服务器没有执行请求的操作 |
| 未初始化 | c-ares 未初始化 |
| 拒绝 | DNS 服务器拒绝查询 |
| SERVFAIL(服务失败) | DNS 服务器返回一般故障 |
| 超时 | 联系 DNS 服务器时超时 |
与dns.lookup()方法不同,该方法可选的第二个参数是一个记录类型,表示您试图解析的 DNS 记录的类型。记录类型有以下几种:“A”、“AAAA”、“MX”、“TXT”、“SRV”、“PTR”、“NS”和“CNAME”。dns.resolve()方法可以将这七种记录类型中的任何一种作为参数;但是,如果没有提供,则默认为“A”类型或 IPv4。
与这种方法相关,但没有显示在解决方案中的是七种记录类型的包装器。这些都是无需传递可选参数就能获得您想要的精确分辨率的简便方法。这些包括dns.resolve4, dns.resolve6, dns.resolveMx, dns.resolveTxt, dns.resolveSrv, dns.resolvePtr, dns.resolveNs和dns.resolveCname。
清单 7-4 。Dns.resolve 和来自 dns.js 的亲戚
var resolveMap = {};
exports.resolve4 = resolveMap.A = resolver('queryA');
exports.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
exports.resolveCname = resolveMap.CNAME = resolver('queryCname');
exports.resolveMx = resolveMap.MX = resolver('queryMx');
exports.resolveNs = resolveMap.NS = resolver('queryNs');
exports.resolveTxt = resolveMap.TXT = resolver('queryTxt');
exports.resolveSrv = resolveMap.SRV = resolver('querySrv');
exports.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
exports.reverse = resolveMap.PTR = resolver('getHostByAddr');
exports.resolve = function(domain, type_, callback_) {
var resolver, callback;
if (typeof type_ == 'string') {
resolver = resolveMap[type_];
callback = callback_;
} else {
resolver = exports.resolve4;
callback = type_;
}
if (typeof resolver === 'function') {
return resolver(domain, callback);
} else {
throw new Error('Unknown type "' + type_ + '"');
}
};对于解决方案的最后一部分,您构建了一个getDomainsReverse函数。这是一个用于dns.reverse函数的包装器,它被设计用来接受一个 IP 地址并找到所有与提供的 IP 地址匹配的域。在您的解决方案中,您将它抽象出来,这样dns.lookup()和dns.resolve()方法都可以重用该函数。然后将检索结果记录到控制台。
您可以看到,DNS 模块为您提供了许多机会来收集离您的位置很远的域和服务器的信息。在解析主机问题的解决方案中,您能够利用dns.lookup、dns.resolve和dns.reverse方法来完成这项任务。
7-2.用缓冲区处理流
问题
您需要使用缓冲区对象来更好地处理流。
解决办法
缓冲区是类似于数组而不仅仅是字符串的二进制数据形式的数据。Buffer 是一个 Node.js 全局对象,用于处理缓冲区。这意味着你可能永远不需要require('buffer'),尽管这是可能的,因为对象的全局性质。为了检查 Buffer 对象的能力,您将创建一个 Node.js 文件,如清单 7-5 所示,它将执行 Buffer 可用的大部分功能。这将使您更好地理解如何处理 Node.js 生态系统中的许多可用缓冲区。
清单 7-5 。Node.js 中的缓冲区
/**
* Buffer
*/
var buffer = new Buffer(16);
console.log('size init', buffer.toString());
buffer = new Buffer([42, 41, 41, 41, 41, 41, 41, 42, 42,4, 41, 41, 0, 0, 7, 77], 'utf-8');
console.log('array init', buffer.toString());
buffer = new Buffer('hello buffer', 'ascii');
console.log(buffer.toString());
buffer = new Buffer('hello buffer', 'ucs2');
console.log(buffer.toString());
buffer = new Buffer('hello buffer', 'base64');
console.log(buffer.toString());
buffer = new Buffer('hello buffer', 'binary');
console.log(buffer.toString());
console.log(JSON.stringify(buffer));
console.log(buffer[1]);
console.log(Buffer.isBuffer('not a buffer'));
console.log(Buffer.isBuffer(buffer));
// allocate size
var buffer = new Buffer(16);
// write to a buffer
console.log(buffer.write('hello again', 'utf-8'));
// append more starting with an offset
console.log(buffer.write(' wut', 11, 'utf8'));
console.log(buffer.toString());
// slice [start, end]
buf = buffer.slice(11, 15);
console.log(buf.toString());
console.log(buffer.length);
console.log(buffer.readUInt8(0));
console.log(buffer.readUInt16LE(0));
console.log(buffer.readUInt16BE(0));
console.log(buffer.readUInt32LE(0));
console.log(buffer.readUInt32BE(0));
console.log(buffer.readInt16LE(0));
console.log(buffer.readInt16BE(0));
console.log(buffer.readInt32LE(0));
console.log(buffer.readInt32BE(0));
console.log(buffer.readFloatLE(0));
console.log(buffer.readFloatBE(0));
console.log(buffer.readDoubleLE(0));
console.log(buffer.readDoubleBE(0));
buffer.fill('4');
console.log(buffer.toString());
var b1 = new Buffer(4);
var b2 = new Buffer(4);
b1.fill('1');
b2.fill('2');
console.log(b1.toString());
console.log(b2.toString());
b2.copy(b1, 2, 2, 4);
console.log(b1.toString());清单 7-5 中的解决方案强调了当你在 Node.js 中使用缓冲区时,你可以使用的许多功能。接下来你将看到一个使用“net”模块的例子,以及当你在客户机和服务器之间通信时,你如何以缓冲区的形式发送数据。
清单 7-6 。使用缓冲区
var net = require('net');
var PORT = 8181;
var server = net.Server(connectionListener);
function connectionListener(conn) {
conn.on('readable', function() {
//buffer
var buf = conn.read();
if (Buffer.isBuffer(buf)) {
console.log('readable buffer: ' , buf);
conn.write('from server');
}
});
conn.on('end', function() {
});
}
server.listen(PORT);
//Connect a socket
var socket = net.createConnection(PORT);
socket.on('data', function(data) {
console.log('data recieved: ', data.toString());
});
socket.on('connect', function() {
socket.end('My Precious');
});
for (var i = 0; i < 2000; i++) {
socket.write('buffer');
}
socket.on('end', function() {
});
socket.on('close', function() {
server.close();
});它是如何工作的
缓冲区是在 Node.js 中处理八位字节流的最佳方式。它们表示从 Node.js 应用内部传输的原始数据,并且可以在 Node.js 中的多个位置找到它们。缓冲区非常通用。正如你在清单 7-5 中看到的,他们有很多方法可以为你提供给定工作的最佳解决方案。
当您希望创建缓冲区时,有几种方法可以使用。在这个解决方案中,您首先通过为缓冲区分配大小var buffer = new Buffer(16);来创建一个缓冲区。然后,您可以通过将一个数组直接传递给缓冲区的构造函数var buffer = new Buffer([42, 42]...);来生成一个新的缓冲区。创建新缓冲区的第三种方法是将一个字符串直接传递给构造函数var buffer = new Buffer('hello world');。构造函数也接受一种编码类型,设置为字符串。如果没有传递编码,那么编码将默认为 utf8。
一旦创建了缓冲区,现在就可以操作缓冲区了。缓冲区对象本身有一些直接可用的方法。这些方法包括Buffer.isEncoding(encoding)、Buffer.isBuffer(object)和Buffer.byteLength(buffer),它们分别评估编码是否按预期设置,查看给定对象是否是缓冲区,并返回缓冲区的字节长度。
除了这些类本身的缓冲方法,还有一些方法,在表 7-2 中列出,你可以在使用缓冲时使用。
表 7-2 。缓冲方法和事件
| 方法(缓冲。) | 描述 |
|---|---|
| 写入(字符串,[偏移量],[长度],[编码]) | 按照给定的偏移量和编码将字符串写入缓冲区。 |
| toString([编码]、[开始]、[结束]) | 从开始到结束,将缓冲区转换为给定范围内具有给定编码的字符串。 |
| toJSON() | 返回缓冲区的 JSON 化版本。 |
| 长度 | 以字节为单位返回缓冲区的大小。 |
| copy([目标缓冲区]、[目标启动]、[sourceStart]、[sourceEnd]) | 将缓冲区数据从源复制到目标:var b1 = new Buffer('1111'); var b2 = new Buffer('2222'); b2.copy(b1, 2, 2, 4); //b2 == 1121 |
| 切片([开始],[结束]) | 在开始和结束参数之间分割缓冲区。产生新的缓冲区。 |
| readUInt8(偏移量,[noassert]) | 从偏移量开始,以无符号 8 位整数形式读取缓冲区。 |
| readUInt16LE(偏移,无修正) | 从偏移量开始,以无符号 16 位整数 little endian 形式读取缓冲区。 |
| readUInt16BE(偏移量[noassert]) | 从偏移量开始,以无符号 16 位整数 big endian 形式读取缓冲区。 |
| readUInt32LE(偏移,无修正) | 从偏移量开始,以无符号 32 位整数小端格式读取缓冲区。 |
| readUInt32BE(偏移量,[noassert]) | 读取缓冲区,从偏移量开始,为无符号 32 位整数,大端格式。 |
| readInt8(偏移量,[noassert]) | 从偏移量开始,以 8 位整数形式读取缓冲区。 |
| readInt16LE(偏移,[no adjustment]) | 从偏移量开始,以 16 位小端方式读取缓冲区。 |
| readInt16BE(偏移量,[noassert]) | 以 16 位 big endian 形式从偏移量开始读取缓冲区。 |
| readInt32LE(位移,[no water]) | 从偏移量开始,以 32 位小端方式读取缓冲区。 |
| readInt32BE(偏移量,[noassert]) | 从偏移量开始,以 32 位大端顺序读取缓冲区。 |
| readFloatLE(offset, [noassert]) | 以 float,little endian 形式从偏移量开始读取缓冲区。 |
| readFloatBE(偏移量,[noassert]) | 读取缓冲区,从偏移量开始,作为一个浮点,大端。 |
| read doublel(offset,[noassert]) | 从偏移量开始,以双精度小端方式读取缓冲区。 |
| readDoubleBE(偏移,[no adjustment]) | 从偏移量开始,以双精度大端方式读取缓冲区。 |
| writeUInt8(值,偏移量,[noassert] | 从偏移量开始,将一个无符号 8 位整数写入缓冲区。 |
| writeUInt16LE(值,偏移量,[noassert]) | 从偏移量 little endian 开始,将一个无符号 16 位整数写入缓冲区。 |
| writeUInt16BE(值,偏移量,[noassert]) | 从偏移量 big endian 开始,将一个无符号 16 位整数写入缓冲区。 |
| writeUInt32LE(值,偏移量,[noassert]) | 从偏移量 little endian 开始,将一个无符号 32 位整数写入缓冲区。 |
| writeUInt32BE(值,偏移量,[noassert]) | 从偏移量 big endian 开始,将一个无符号 32 位整数写入缓冲区。 |
| writeInt8(值,偏移量,[noassert]) | 从偏移量开始,将一个 8 位整数写入缓冲区。 |
| writeInt16LE(值,偏移,[no improved]) | 从偏移量 little endian 开始,将一个 16 位整数写入缓冲区。 |
| writeInt16BE(值,偏移量,[noassert]) | 从偏移量 big endian 开始,将一个 16 位整数写入缓冲区。 |
| writeInt32LE(值、偏移、无改善) | 从偏移量 little endian 开始,将一个 32 位整数写入缓冲区。 |
| writeInt32BE(值,偏移量,[noassert]) | 从偏移量 big endian 开始,将一个 32 位整数写入缓冲区。 |
| writeFloatLE(值,偏移量,[noassert]) | 从偏移量 little endian 开始,将一个浮点值写入缓冲区。 |
| siwriteFloatBE(值,偏移量,[no asset]) | 从偏移量 big endian 开始,将一个浮点值写入缓冲区。 |
| writeDoubleLE(value,offset,[no asset]) | 从偏移量 little endian 开始,将一个 double 值写入缓冲区。 |
| writeDoubleBE(值,偏移量,[no asset]) | 从偏移量 big endian 开始,将一个 double 值写入缓冲区。 |
| fill(值,[偏移量],[结束]) | 用从偏移量到结束范围指定的值填充缓冲区。 |
缓冲区中有许多方法可用于非常特殊的目的。例如,如果您需要以 little endian 格式读写无符号 32 位整数,缓冲区可以做到这一点。虽然这些方法非常灵活,但在大多数情况下,你会使用如清单 7-4 所示的缓冲区。这是一个“网络”服务器和客户端互相发送数据的例子。数据变成一个可读的流,这是一个缓冲区。您将能够对任何缓冲区执行一节中描述的方法,从而允许您操作应用中使用的数据和流。
7-3.使用 Node.js 进行聚类
问题
您希望构建一个进程集群来更有效地运行您的应用。
解决办法
Node.js 为集群提供了一个解决方案。在撰写本文时,该特性仍处于试验阶段,但它能够将单线程 Node.js 应用转变为在您的机器上利用多个内核的应用。通过这种方式,您可以将 Node.js 任务委托给不同的线程,从而实现更大的可伸缩性。在这个解决方案中,您将生成一个使用集群模块的 Node.js 应用。第一个例子是一个独立的解决方案,它将分割一个简单的 HTTP 服务器,并将各种集群方法的结果记录到您的控制台。
清单 7-7 。使聚集
/**
* Clustering
*/
var cluster = require('cluster'),
http = require('http'),
cpuCount = require('os').cpus().length;
if (cluster.isMaster) {
for (var i = 0; i < cpuCount; i++) {
cluster.fork();
}
cluster.on('fork', function(worker) {
console.log(worker + ' worker is forked');
});
cluster.on('listening', function(worker, address) {
console.log(worker + ' is listening on ' + address);
});
cluster.on('online', function(worker) {
console.log(worker + ' is online');
});
cluster.on('disconnect', function(worker) {
console.log(worker + ' disconnected');
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}现在,您将配置集群来执行第二个 Node.js 文件,对机器上的每个内核执行一次。
清单 7-8 。集群化 Node.js 流程
/**
* Clustering
*/
var cluster = require('cluster'),
cpuCount = require('os').cpus().length;
cluster.setupMaster({
exec: '7-3-3.js'
});
if (cluster.isMaster) {
for (var i = 0; i < cpuCount; i++) {
cluster.fork();
}
cluster.on('fork', function(worker) {
console.log(worker + ' worker is forked');
});
cluster.on('listening', function(worker, address) {
console.log(worker + ' is listening on ' + address);
});
cluster.on('online', function(worker) {
console.log(worker + ' is online');
});
cluster.on('disconnect', function(worker) {
console.log(worker + ' disconnected');
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
}清单 7-9 ,工作进程,如下所示。
清单 7-9 。工作进程
var http = require('http');
http.createServer(function(req, res) {
console.log(req.url);
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);它是如何工作的
Node.js 中的集群本质上是利用一个 Node.js 模块和利用child_process.fork()函数分割工作进程的解决方案,同时维护主进程和工作进程之间的引用和通信。工作进程可以是 TCP 或 HTTP 服务器,请求由主进程处理。然后,这个主进程利用循环负载平衡在服务器中分配负载。它通过监听连接,然后调用 distribute 方法并将处理交给工作进程来完成。
清单 7-10 。主监听,然后分配负载
this.server.once('listening', function() {
self.handle = self.server._handle;
self.handle.onconnection = self.distribute.bind(self);
self.server._handle = null;
self.server = null;
});
RoundRobinHandle.prototype.distribute = function(handle) {
this.handles.push(handle);
var worker = this.free.shift();
if (worker) this.handoff(worker);
};
RoundRobinHandle.prototype.handoff = function(worker) {
if (worker.id in this.all === false) {
return; // Worker is closing (or has closed) the server.
}
var handle = this.handles.shift();
if (typeof handle === 'undefined') {
this.free.push(worker); // Add to ready queue again.
return;
}
var message = { act: 'newconn', key: this.key };
var self = this;
sendHelper(worker.process, message, handle, function(reply) {
if (reply.accepted)
handle.close();
else
self.distribute(handle); // Worker is shutting down. Send to another.
self.handoff(worker);
});
};在您的解决方案中,您主要使用主流程。在这两种情况下,您都有一个简单的 HTTP 服务器,当向服务器地址发出请求时,它会以“hello world”作为响应。导入集群模块后,使用cluster.isMaster 检查该进程是否是集群的主进程。现在,您可以通过使用“os”模块检查您的计算机拥有的内核数量来查看您应该在您的计算机上创建多少个集群。对于每个 CPU,您派生一个名为cluster.fork()的新工作进程。因为底层框架仍然生成一个新的子进程,它仍然是 v8 的一个新实例,所以您可以假设每个 worker 的启动时间在大多数情况下将大于 0 并且小于 100 ms。它还会在启动时为每个进程生成大约 10 MB 的内存消耗。
您现在知道这是主流程,因此您能够绑定到将被传递到主流程的事件。感兴趣的事件是“fork”、“listening”、“exit”、“online”和“setup”。“fork”事件在工作进程被成功分叉时发出,并为被分叉的进程提供工作对象。一旦创建并运行分叉流程,就会发生“在线”事件。一旦工作者开始监听,就发送“监听”事件。当工作进程终止时,将发出“exit”事件。如果发生这种情况,您可能需要调用。fork()方法来替换关闭的工作线程。
集群是一个强大的模块,它允许您在机器上的多个进程之间分配服务器的负载。随着 Node.js 应用的增长,该功能在创建可伸缩应用时会变得很重要。
7-4.使用查询字符串
问题
您用 Node.js 构建了一个 web 服务器,您希望巧妙地处理通过 HTTP 请求传递给应用的查询字符串的各种差异。
解决办法
Node.js 有一个查询字符串模块,允许您为 Node.js 应用正确解析和编码查询字符串参数。
清单 7-11 。使用查询字符串模块
/**
* querystrings
*/
var qs = require('querystring');
var incomingQS = [ 'foo=bar&foo=baz',
'trademark=%E2%84%A2',
'%7BLOTR%7D=frodo%20baggins'];
incomingQS.forEach(function(q) {
console.log(qs.parse(q));
});
var outgoingQS = { good: 'night', v: '0.10.12', trademark: '™'};
console.log(qs.stringify(outgoingQS));
var newQS = qs.stringify(outgoingQS, '|', '∼');
console.log(newQS);
console.log(qs.parse(newQS));您可以将任意查询字符串作为输入,然后将其解析为一个对象。您还能够获取任意对象,并将其解析为 URL 安全查询字符串。
它是如何工作的
虽然查询字符串模块不是一个巨大的模块,但它只导出四个方法。它为处理查询字符串提供了一个非常有用的解决方案。在这个解决方案中,您首先需要'querystring'模块。这个模块提供了几种方法来帮助处理应用中的查询字符串。
首先,querystring.parse函数接受一个字符串,并可选地覆盖分隔符,默认为&和等于。还有一个 options 对象,允许您覆盖要处理的 1000 个最大键(maxKeys)的缺省值。对于查询字符串中的每个键,‘querystring’模块将尝试通过首先使用 JavaScript 自带的decodeURIComponent()函数来解析值。如果这产生了一个错误,那么这个模块将会转移到它自己的被称为querystring.unescape的实现上。
其次,querystring.stringify函数将接受您希望编码到querystring中的对象。或者,该方法还将允许您覆盖默认的&号和等号分隔符。querystring.stringify方法将解析对象,将其转换成字符串,然后调用模块的QueryString.escape方法。这个方法只是 JavaScript 的encodeURIComponent()的一个包装器。
7-5.用“进程”处理事件
问题
您希望能够在 Node.js 应用中全局处理事件。
解决办法
Node.js 流程模块是一个全局对象,可以在 Node.js 应用中的任何地方访问。在这个解决方案中,您可以想象一种情况,其中您有一个模块和一个辅助模块。主模块绑定到初始化事件,并开始调用辅助模块的某些方法。主模块看起来像清单 7-12 中所示的模块,辅助模块看起来像清单 7-13 中所示的模块。
清单 7-12 。主模块,处理流程
/**
* using the process
*/
function log(msg) {
if (typeof msg === 'object') {
msg = JSON.stringify(msg);
}
process.stdout.write(msg + '\n');
}
//add listeners
process.on('power::init', function() {
log('power initialized');
});
process.on('power::begin', function() {
log('power calc beginning');
});
process.on('exit', function() {
log(process.uptime());
log('process exiting...');
});
process.on('uncaughtException', function(err) {
log('error in process ' + err.message + '\n');
});
log(process.cwd());
process.chdir('..');
log(process.cwd());
log(process.execPath);
log(process.env.HOME);
log(process.version);
log(process.versions);
log(process.config);
log(process.pid);
log(process.platform);
log(process.memoryUsage());
log(process.arch);
var pow = new require('./power');
var out = pow.power(42, 42);
log(out);
// throws
setTimeout(pow.error, 1e3);清单 7-13 。辅助模块
/**
* power module
*/
process.emit('power::init');
exports.power = function(base, exponent) {
var result = 1;
process.emit('power::begin');
for (var count = 0; count < exponent; count++)
result *= base;
return result;
};它是如何工作的
Node.js 流程对象可以为您的应用获取有价值的信息和实用程序。流程对象是全局的,是一个EventEmitter。这就是为什么在上面的例子中,您能够从 power.js 文件中发出'power::init'。你也通过调用process.on('power::init', callback)来绑定到这个。然后绑定到其他事件。首先,绑定到另一个自定义事件,该事件是在开始执行 power.js 模块的 power 函数时发出的。
另外两个事件是 Node.js 流程的内置事件。首先,绑定到'exit'事件。这将在流程准备退出时触发,给你最后一次机会记录错误或通知用户流程即将结束。您侦听的另一个内置事件是'uncaughtException'事件。该事件由任何异常触发,否则这些异常会出现在控制台上并使您的应用崩溃。在该解决方案中,您可以通过尝试调用 power.js 模块上不存在的方法来触发该事件。
流程模块不仅仅处理事件。事实上,它可以提供大量与您当前 Node.js 流程相关的信息,其中许多信息您可以在创建解决方案时加以利用。表 7-3 详述了流程对象上的这些其他方法和属性。
表 7-3 。过程对象方法和属性
| 方法 | 描述 |
|---|---|
| 中止() | 此方法将中止该过程。 |
| 拱门 | 您系统的架构。 |
| 阿尔戈夫 | 这是实例化 Node.js 流程的参数。在解析传递给应用的参数时,您已经看到了这一点。 |
| 总监(主任) | 更改您的进程当前工作的目录。 |
| 配置 | 列出了 Node.js 应用的配置。 |
| cwd() | 打印进程的当前工作目录。 |
| 包封/包围(动词 envelop 的简写) | 将系统中的环境变量作为对象列出。 |
| execPath | 这是系统上 Node.js 可执行文件的路径。 |
| 退出([代码]) | 用指定的代码发出退出事件。 |
| 格吉德() | 获取进程的组 ID。在 Windows 上不可用。 |
| getgroups() | 获取进程补充组的组 ID 数组。在 Windows 上不可用。 |
| 它是() | 获取进程的用户 ID。在 Windows 上不可用。 |
| hrtime([hrtime]) | 过去任意时期以来的高分辨率时间数组(秒、纳秒)。这可以与先前的 hrtime 读数一起使用,以获得差值。 |
| 初始组 | 读取/etc/groups。在 Windows 上不可用。 |
| kill(进程 id,[信号]) | 向进程 ID 发送信号。 |
| maxTickDepth | 您可以使用它来设置在允许事件循环处理之前要运行的节拍数。这阻止了使用。锁定 I/O 后的下一个时钟周期 |
| memoryUsage() | 进程中使用的内存字节数。 |
| nextTick(回调) | 下一次在事件循环中,回调将被执行。可以这样做: |
function definitelyAsync(arg, cb) {
if (arg) {
process.nextTick(cb);
return;
}
fs.stat('file', cb);
} |
| pid | 进程标识符。 |
| 平台 | 列出运行进程的平台。 |
| setgid() | 设置进程的组 ID。在 Windows 上不可用。 |
| 集合组 | 为进程设置组数组。在 Windows 上不可用。 |
| setuid() | 设置进程的用户 ID。在 Windows 上不可用。 |
| 标准错误 | 标准误差;这是一个可写流。 |
| 标准输入设备 | 表示标准输入的可读流。
function log(msg) {
if (typeof msg === 'object') {
msg = JSON.stringify(msg);
}
process.stdout.write(msg + '\n');
} |
| 标准输出 | 这是您的流程的标准输出,并且是一个可写流。您可以通过创建您的 log()函数来重新创建控制台日志记录:
function log(msg) {
if (typeof msg === 'object') {
msg = JSON.stringify(msg);
}
process.stdout.write(msg + '\n');
} |
| 标题 | 流程的标题。 |
| umask([mask]) | 进程的文件模式创建掩码的 Setter 和 getter。 |
| 正常运行时间() | Node 已经运行的秒数(不是毫秒)。 |
| 版本 | 打印进程正在使用的 Node.js 版本。 |
| 版本 | 列出包含 Node.js 版本及其依赖项的对象。 |
7-6.使用计时器
问题
您希望能够在 Node.js 应用中利用计时器来控制流。
解决办法
控制任何应用中特定进程的时间是非常关键的,包括那些用 Node.js 构建的应用。如果您在 web 应用中使用过计时器,那么在 Node.js 中使用计时器应该很熟悉,因为有些方法在浏览器中也可以使用。
在这个解决方案中,您将创建一个应用,它将利用计时器来轮询一个虚构的远程资源。这个解决方案将代表一个场景,其中您需要从远程队列获取数据。有几种解决方案可以按时间间隔进行轮询,或者简单地利用计时器在事件循环中有效地调用方法。
清单 7-14 。使用计时器
/**
* Using Timers
*/
var count = 0;
var getMockData = function(callback) {
var obj = {
status: 'lookin good',
data: [
"item0",
"item1"
],
numberOfCalls: count++
};
return callback(null, obj);
};
var onDataSuccess = function(err, data) {
if (err) console.log(err);
if (data.numberOfCalls > 15) clearInterval(intrvl);
console.log(data);
};
// getMockData(onDataSuccess);
setImmediate(getMockData, onDataSuccess);
var tmr = setTimeout(getMockData, 2e3, onDataSuccess);
tmr.unref();
var intrvl = setInterval(getMockData, 50, onDataSuccess);它是如何工作的
Node.js 中有几个可以使用的计时器。首先,有一组可以在 web 浏览器中找到的计时器。这些是setTimeout和setInterval及其相应的clearTimeout和clearInterval功能。
setTimeout是一种在给定时间延迟后安排一次性事件的方式。一个setTimeout调用的结构至少有两个参数。第一个参数是您希望在计时器触发时执行的回调。第二个是等待回调执行的毫秒数。或者,您可以向函数添加额外的参数,这些参数将在计时器执行时应用于回调。通过调用clearTimeout并传递一个对初始超时定时器的引用,可以取消setTimeout。
注意由于 JavaScript Node.js 事件循环,您无法直接依赖回调执行的时间。Node.js 将尝试在接近规定的时间执行回调,但它可能不会在精确的时间间隔执行。
setInterval是setTimeout的亲戚。它通过提供一种将功能的执行延迟一段设定时间的机制,以类似的方式发挥作用。然而,使用setInterval,该函数将在相同的时间间隔内重复执行,直到clearInterval被调用。在上面的解决方案中,这是在长时间运行的流程中用于轮询的情况。理论上,您可以在 30 秒、3 分钟或每小时的长时间轮询中运行一个时间间隔,并让该过程继续运行。然而在解决方案中,您以很短的时间间隔(< 1 秒)运行该间隔,并在它执行了 15 次后清除它。
setInterval和setTimeout方法都附加了两个额外的方法。这些是unref()和ref()。如果计时器是 event.loop 上剩下的唯一计时器,那么unref()方法允许 Node.js 进程终止。而ref()方法则相反,它会暂停进程,直到计时器执行完毕。要看到这一点,您可以注意到,在解决方案中的两秒延迟setTimeout方法之后,您立即调用该计时器的unref()方法。这意味着这个定时器永远不会执行。因为从setInterval开始的时间间隔在两秒钟过去之前就已经结束并被清除,所以事件循环中不再有其他计时器,该过程优雅地退出。
setImmediate是 Node.js 的另一个计时机制。这对于调用近即时方法很有用,类似于process.nextTick()函数的操作方式。setImmediate会将函数排在当前事件循环中任何 I/O 绑定回调函数的后面进行处理。这与nextTick的操作略有不同,因为它会将其执行推到事件循环的前面。这意味着setImmediate是一种更好的方式来执行一个不会锁定 I/O 进程的方法。如果您正在运行一个需要一定程度的 CPU 使用率的递归函数,这将特别有用,因为这不会阻止这些操作在回调之间发生。
7-7.使用 V8 调试器
问题
您需要单步调试 Node.js 应用。
解决办法
Node.js 运行在 Google 的 V8 上,V8 有内置的调试机制。因此,Node.js 允许您利用该工具调试源代码。您将创建一个解决方案,帮助您了解调试器是如何工作的,以及它能为您的代码带来哪些启示。清单 7-15 显示了一个简单的 HTTP 服务器,它需要第二个模块(如清单 7-16 所示)。
清单 7-15 。HTTP 服务器
/**
* Debugging
*/
var http = require('http'),
mod = require('./7-7-2');
server = http.createServer(function(req, res) {
if (req.url === '/') {
debugger;
mod.doSomething(function(err, data) {
if (err) res.end('an error occured');
res.end(JSON.stringify(data));
});
} else {
res.end('404');
}
});
server.listen(8080);清单 7-16 。所需模块
/**
* Debugging
*/
exports.doSomething = function(callback) {
debugger;
callback(null, { status: 'okay', data: ['a', 'b', 'c']});
};有些东西可能与典型的 Node.js 应用略有不同;特别是你可以看到一些“调试器”陈述。这些指令告诉 V8 调试机制暂停程序的执行。这个过程从用“debug”标志启动 Node.js 应用开始。
它是如何工作的
Google 设计了支持 Node.js 的 V8 JavaScript 引擎,允许调试引擎中执行的 JavaScript。Node.js 以两种方式支持 V8 调试。一种方法是以创建调试器的方式实现 V8 调试器协议,监听 TCP 端口。如果您正在创建或使用协调使用该协议的第三方调试工具,这将非常有用。为此,使用命令$ node --debug 7-7-1.js启动 Node.js 应用 7-7-1.js。这将启动调试器,并在localhost:5858上监听调试器的挂钩。这允许创建与调试器通信的调试客户端。幸运的是,Node.js 自带了自己的 V8 调试器客户端。您可以通过在控制台中键入$ node debug 来访问在调试模式下使用- -debug 标志启动的应用。
通过使用“debug”参数启动 Node.js 应用,可以访问 Node.js 内置调试器。
清单 7-17 。启动 Node.js 调试 CLI
$ node debug 7-7-1.js这将启动您的应用,但附加了调试器。控制台中的输出将显示调试器已经开始监听,并且它将显示 JavaScript 代码的第一行,默认情况下调试器将中断该行。
清单 7-18 。调试器的初始状态
< debugger listening on port 5858
connecting... ok
break in 7-7-1.js:5
3 */
4
5 var http = require('http'),
6 mod = require('./7-7-2');
7
debug>您现在有一个“debug>”提示。这是调试器的命令行界面。您可以按照下面的步骤完成调试的基础。首先,您可以向应用中的对象或属性添加观察器。要做到这一点,你可以输入“watch ”,然后输入任何你想观看的表情。
debug> watch('expression')因此,在您的解决方案中,您可以通过使用 watch 命令并传递“req.url”作为表达式来监视请求 URL。
debug> watch('req.url')您还可以列出调试器会话中当前活动的所有观察器。结果会将活动的观察器及其值打印到控制台。当前值被赋予 JavaScript 代码暂停的直接上下文。
debug> watchers
0: req.url = "<error>"回想一下,在您的应用代码中,您创建了两个名为'debugger;'的地方。它将在应用中的这些点暂停执行。但是,有时您可能不希望添加调试器语句,而只想在代码中设置断点。为此,调试器有几个可用的断点方法。要在当前行设置断点,只需在调试控制台中键入setBreakpoint()。或者,您可以使用简写的sb()来设置断点。setBreakpoint方法也接受一个行号,因此您可以预先确定一行来中断。您可以在代码中通过在server.listen(8080)方法上设置一个断点来做到这一点。
debug> sb(21)
1 /**
2 * Debugging
3 */
4
5 var http = require('http'),
6 mod = require('./7-7-2');
7
8 server = http.createServer(function(req, res) {
9 if (req.url === '/') {
10 debugger;您还可以中断将加载到您的应用中的另一个文件。为此,将文件名和行号传递给setBreakpoint方法。
debug> sb('7-7-2.js', 5)
Warning: script '7-7-2.js' was not loaded yet.
1 /**
2 * Debugging
3 */
4
5 var http = require('http'),
6 mod = require('./7-7-2');
7
8 server = http.createServer(function(req, res) {
9 if (req.url === '/') {
10 debugger;在这里您可以看到,您已经在文件 7-7-2.js 中的第一行代码上设置了断点。一旦您继续执行程序,一旦命中该行代码,断点将再次暂停程序的执行。
此时,您已经准备好使用调试器在应用中导航了。与大多数调试器一样,调试器公开允许您逐句通过并继续执行代码的方法。最细化的方法是命令中的步骤。这是通过键入'step'或's'来调用的,简称为。从调试实例执行的开始,如果您单步执行,它会将您移动到下一个执行区域。在这个实例中,它已经移动到 module.js 文件中,并开始在源代码中添加您需要的模块。
debug> s
break in module.js:380
Watchers:
0: req.url = "<error>"
378
379 function require(path) {
380 return self.require(path);
381 }
382从这里你会想继续。继续执行将一直运行,直到遇到下一个断点。如果没有其他断点,应用将正常运行,直到您用pause命令手动暂停它。可以通过“cont'”或“c'”来触发延续。在您的示例中,这将引导您完成模块导入代码,并到达断点,断点是您在“7-7-2.js”文件的第 5 行设置的。
debug> c
break in 7-7-2.js:5
Watchers:
0: req.url = "<error>"
3 */
4
5 exports.doSomething = function(callback) {
6 debugger;
7 callback(null, { status: 'okay', data: ['a', 'b', 'c']});
debug> c
break in 7-7-1.js:21
Watchers:
0: req.url = "<error>"
19 });
20
*21 server.listen(8080);
22
23再继续一次,会碰到你在‘7-7-1 . js’第 21 行设置的断点;这是您设置的最后一个断点。但是,一旦与 HTTP 服务器建立了连接,就会遇到一些调试器语句。继续完成后,您可以向您的 web 服务器发出请求,'``http://localhost:8080/’。因为有了debugger;语句,这将在连接监听器回调的精确位置暂停执行。
debug> c
break in 7-7-1.js:10
Watchers:
0: req.url = "/"
8 server = http.createServer(function(req, res) {
9 if (req.url === '/') {
10 debugger;
11 mod.doSomething(function(err, data) {
12 if (err) res.end('an error occured');从这里,你可以进入下一次执行。这是使用调试器中的“next”或“n”命令完成的。执行两次“T2”,你就会在调试器中结束;' 7-7-2.js '模块中的语句。
debug> n
break in 7-7-1.js:11
Watchers:
0: req.url = "/"
9 if (req.url === '/') {
10 debugger;
11 mod. doSomething (function(err, data) {
12 if (err) res.end('an error occured');
13
debug> n
break in 7-7-2.js:6
Watchers:
0: req.url = "<error>"
4
5 exports.doSomething = function(callback) {
6 debugger;
7 callback(null, { status: 'okay', data: ['a', 'b', 'c']});
8 };现在,您可以使用'out'或('【T1 ')'命令跳出此方法。
debug> o
break in 7-7-1.js:19
Watchers:
0: req.url = "/"
17 res.end('404');
18 }
19});
20
21 server.listen(8080);除了单步、下一步、继续和退出,还有'pause'命令。这将暂停当时正在运行的任何代码的执行。
当您单步执行代码时,有时需要获得更多关于应用中发生的事情的信息。调试器对此也有实用工具。首先,当你在一个断点处暂停时,如果你想看到周围更多的代码,你可以通过使用'list(n)'命令来实现。这将显示当前暂停位置前后由“n”行包围的代码,这对于收集调试器中当前正在发生的事情的更多上下文非常有用。另一个有用的特性是'backtrace'(' T2 ')命令。这将显示程序中当前点的执行路径的轨迹。
清单 7-19 。7-7-2.js 模块的 doSomething 方法中的回溯示例
debug> bt
#0 exports.doSomething 7-7-2.js:6:2
#1 7-7-1.js:11:7您也可以使用'scripts'命令查看加载的文件。重要的是,如果您需要更深入地研究代码,您可以通过使用'repl'命令来使用调试器的读取-评估-打印循环(REPL)模块。
使用内置命令行界面调试 Node.js 应用被视为高优先级,以便使用 V8 调试器调试您的应用。当您跟踪代码中的异常和错误时,您会发现这些工具非常有用。
7-8.解析 URL
问题
您希望能够解析 Node.js HTTP 服务器应用中的 URL。
解决办法
Node.js 附带了一个 URL 模块,可以用来解析 URL 并收集其中包含的信息。看看这是如何工作的一个解决方案(见清单 7-20 )将告诉你如何解析一个任意的 URL。
清单 7-20 。解析任意 URL
/**
* parse url
*/
var url = require('url');
var theurl = 'http://who:ami@hostname:1234/a/b/c/d/?d=e#f=g';
var urlParsed = url.parse(theurl, true, true);
console.log('protocol', urlParsed.protocol);
console.log('slashes', urlParsed.slashes);
console.log('auth', urlParsed.auth);
console.log('host', urlParsed.host);
console.log('port', urlParsed.port);
console.log('hostname', urlParsed.hostname);
console.log('hash', urlParsed.hash);
console.log('search', urlParsed.search);
console.log('query', urlParsed.query);
console.log('pathname', urlParsed.pathname);
console.log('path', urlParsed.path);
console.log('href', urlParsed.href);
console.log(url.resolve('/a/b/c/', 'd'));7-20 的结果
$ node 7-8-2.js
protocol http:
slashes true
auth who:ami
host hostname:1234
port 1234
hostname hostname
hash #f=g
search ?d=e
query { d: 'e' }
pathname /a/b/c/d/
path /a/b/c/d/?d=e
href http://who:ami@hostname:1234/a/b/c/d/?d=e#f=g
/a/b/c/d在实践中使用它,你可以想象一个 HTTP 服务器,不像清单 7-21 ,它需要 URL 被解析,这样你就可以协调正确的文件来服务于HTTP.response()中的客户端。
清单 7-21 。使用 URL 模块
/**
* Parsing URLS
*/
var http = require('http'),
fs = require('fs'),
url = require('url');
var server = http.createServer(function(req, res) {
var urlParsed = url.parse(req.url,true, true);
fs.readFile(urlParsed.path.split('/')[1], function(err, data) {
if (err) {
res.statusCode = 404;
res.end(http.STATUS_CODES[404]);
}
var ursplit = urlParsed.path.split('.');
var ext = ursplit[ursplit.length - 1];
switch(ext) {
case 'htm':
case 'html':
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(data);
break;
case 'js':
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.end(data);
break;
case 'css':
res.writeHead(200, {'Content-Type': 'text/css'});
res.end(data);
break;
case 'json':
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(data);
break;
default:
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(data);
}
});
}).listen(8080);它是如何工作的
使用 URL 模块为您提供了三种处理 URL 的方法。首先你使用了url.parse()方法。该方法接受一个 URL 字符串,并返回一个经过解析的 URL 对象。解析后的 URL 对象可以采用表 7-4 中所示的属性。
表 7-4 。解析的 URL 对象属性
| 财产 | 描述 |
|---|---|
| 。作家(author 的简写) | URL 的授权部分。用户名:密码 |
| 。混杂 | URL 中存在的任何片段。 |
| 。宿主 | URL 的完整主机名和端口。 |
| 。主机名 | URL 中主机的全名。 |
| 。超链接 | 完整的网址。 |
| 。小路 | 路径名和搜索相结合。 |
| 。路径名 | URL 的主机名和端口部分后面的完整路径名。 |
| 。港口 | URL 中指定的端口。 |
| 。草案 | 请求的协议。 |
| 。询问 | 不带“?”的查询字符串。可以被解析为一个对象。 |
| 。搜索 | URL 的查询字符串部分。 |
HTTP 服务器示例中使用了解析的对象来解析路径,以便服务器可以读入文件类型并为内容提供适当的 mime 类型。URL 路由也有很好的用途,可以从解析 URL 中受益。
如果您正在处理一个解析的 URL,并且您想要将该对象转换回一个正确的 URL,无论您是将 URL 返回给客户端还是出于其他目的,您都可以通过调用该对象上的url.format()函数从一个解析的 URL 对象创建一个 URL。这将重新格式化对象,不包括返回到 URL 的href。
第三种可以使用的方法是url.resolve(from, to)函数。该函数将尝试解析路径,就像 web 浏览器一样。
可以看到,如果要在 Node.js 应用中处理 URL,应该利用 URL 模块内置的特性。它提供了解析和格式化应用中需要的任何 URL 所需的工具。
7-9.使用控制台
问题
您希望利用控制台来记录 Node.js 应用中的细节、指标和断言。
解决办法
您可能熟悉一些控制台函数,因为大多数人在构建 Node.js 应用时至少会使用console.log()函数,并且您已经在本书其他部分的许多示例中看到了它的使用。为了了解如何使用控制台,清单 7-22 展示了 Node.js 开发人员可以使用的所有不同方法。
清单 7-22 。使用控制台
/**
* Console
*/
console.log('console usage in Node.js');
console.info('console.info writes the', 'same as console.log');
console.error('same as console.log but writes to stderr');
console.warn('same as console.err');
console.time('timer');
setTimeout(console.timeEnd, 2e3, 'timer');
console.dir({ name: 'console.dir', logs: ['the', 'string representation', 'of objects']});
var yo = 'yo';
console.trace(yo);
try {
console.assert(1 === '1', 'one does not equal one');
} catch(ex) {
console.error('an error occured: ', ex.message);
}控制台结果
7|⇒ node 7-9-1.js
console usage in Node.js
console.info writes the same as console.log
same as console.log but writes to stderr
same as console.err
{ name: 'console.dir',
logs: [ 'the', 'string representation', 'of objects' ] }
Trace: yo
at Object.<anonymous> (/Users/gack/Dropbox/book/code/7/7-9-1.js:20:9)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:901:3
an error occured: one does not equal one
timer: 2001ms它是如何工作的
Node.js 中的 console 对象是写入stdout和stderr 的方法。最常见的控制台功能有console.log、console.err、console.warn、console.dir和console.info。这些函数直接处理日志信息以向用户提供信息,它们直接处理 Node.js 进程的stdout和stderr。
控制台对象源
Console.prototype.log = function() {
this._stdout.write(util.format.apply(this, arguments) + '\n');
};
Console.prototype.info = Console.prototype.log;
Console.prototype.warn = function() {
this._stderr.write(util.format.apply(this, arguments) + '\n');
};
Console.prototype.error = Console.prototype.warn;
Console.prototype.dir = function(object) {
this._stdout.write(util.inspect(object, { customInspect: false }) + '\n');
};在 Node.js 中实现这些方法并不重要。但是,还有一些其他的控制台方法可能会有用。
其中之一就是一对console.time和console.timeEnd。每个函数都有一个标签,告诉 Node.js 跟踪从调用console.time('label')到console.timeEnd('label')之间的时间。曾经的console。它将记录事件之间经过的毫秒数。
Console.prototype.time = function(label) {
this._times[label] = Date.now();
};
Console.prototype.timeEnd = function(label) {
var time = this._times[label];
if (!time) {
throw new Error('No such label: ' + label);
}
var duration = Date.now() - time;
this.log('%s: %dms', label, duration);
};Console.trace() 是一个打印当前堆栈跟踪的函数,应用于作为标签传递的参数。这是通过基于当前堆栈创建一个新的错误对象并设置细节来实现的。
Console.prototype.trace = function() {
// TODO probably can to do this better with V8's debug object once that is
// exposed.
var err = new Error;
err.name = 'Trace';
err.message = util.format.apply(this, arguments);
Error.captureStackTrace(err, arguments.callee);
this.error(err.stack);
};Console.assert ,是assert.ok()的包装器,如果断言失败,它将抛出一个错误。在这个解决方案中,您创建了一个您知道会失败的断言,并且在捕获到异常时记录了错误消息。