TypeScript 是微软开发的开源编程语言，在全世界的开发者中流行。本质上来说 typescript 是 JavaScript 的超集，为其添加了很多功能。标志性的就是添加了 static type 静态类型定义，它可以声明期待的数据类型，例如：声明 function 中的期待的传入数据类型，期望的返回值的类型，object 的结构及 property 的数据类型等。typescript 是一个强大的工具，给 JavaScript 项目开启了新的世界。它使我们的代码更加的安全有力，在程序发布前就可以防止很多的 bug 出现。在编写代码期间就能够及时反映出问题点，并且它已经被整合在现代的编译器中，如 VS code。语法说明下面是一个 typescript 代码示例：type User = { name: string; age: number; }; function isAdult(user: User): boolean { return user.age >= 18; } const justine: User = { name: 'Justine', age: 23, }; const isJustineAnAdult: boolean = isAdult(justine);首先我们通过 type 关键词定义一个自定义的 object 类型 User，其中定义了 User 的两个 properties 及其数据类型。然后在定义的 function isAudlt 中接收一个参数，此参数类型为 User 类型，然后返回值类型为 boolean 类型。然后我们定义了 justine object 且其类型为 User。在其中定义的两个 properties 也符合 User 中的类型。最后定义了变量 isJustineAnAdult 且其数据类型为 boolean，将 isAudlt 的返回值赋给它。如果我们在编程中没有使用指定的数据类型，typescript 将会给出警告提醒我们错误的地方，比如上面的示例中修改 justine 的 age 为一个字符串：在 vs code 中会提示此处有错误。另一方面也并不需要将所有地方的数据类型都做出声明，typescript 会自动推断出需要的类型。例如：即使我们不给 isJustineAnAdult 声明 boolean 类型，typescript 也会自动推断它是 boolean 类型的。那么如何运行 typescript 代码呢？首先需要通过 npm 安装 typescript，它会安装我们需要的一些可执行程序：npm install typescript 下面我们可以通过 tsc 命令将 typescript 代码转换为标准 JavaScript 代码。我们将上面示例中的代码保存为 demo.ts 文件，然后通过 npx 调用 tsc 命令(如果以 global 方式安装 typescript 则可以直接运行命令)：npx tsc demo.ts 以上命令会转换 JavaScript 代码并自动创建一个 demo.js 文件，此文件可以在 node.js 运行:function isAdult(user) { return user.age >= 18; } var justine = { name: 'Justine', age: 23 }; var isJustineAnAdult = isAdult(justine);如果 typescript 代码中包含错误，则使用上面的转换命令会出现报错信息：% npx tsc demo.ts demo.ts:12:5 - error TS2322: Type 'string' is not assignable to type 'number'. 12 age: '23', ~~ demo.ts:3:5 3 age: number; ~~ The expected type comes from property 'age' which is declared here on type 'User' Found 1 error.可以看到 typescript 成功的防止将包含潜在错误代码的程序发布出去。更多介绍除此之外 typescript 也包含了很多其他很好的工具，如：interfaces, classes, utility types 等。更多使用方法参考 typescript 官方文档：https://www.typescriptlang.org/docs。typescript 在 node.js 生态中已经有了很好的发展，且被用于很多开源项目和框架中。以下是一些使用 typescript 的开源项目：NestJS - robust and fully-featured framework that makes creating scalable and well-architected systems easy and pleasantTypeORM - great ORM influenced by other well-known tools from other languages like Hibernate, Doctrine or Entity FrameworkPrisma - next-generation ORM featuring a declarative data model, generated migrations and fully type-safe database queriesRxJS - widely used library for reactive programming总结通过这 14 篇教程，我们介绍了 node.js 的基本语法和相关使用场景。后期可以在具体项目中进行更加详细的体验。
node.js 入门教程之十三 -- environment，error handler 和 log object
environment 运行环境production environment 产品环境和 development environment 开发环境可以设置不同的配置。node.js 默认为 development env 开发环境。通过设置环境变量：NODE_ENV=production 可以告诉 node.js 当前为产品环境。Linux 中可以通过如下指令修改：export NODE_ENV=production 这样在当前 shell 中就会以 production 模式运行 node 程序，如果要永久修改此环境变量可以将命令写入 ~/.bashrc 文件。也可以在运行 node 程序时定义 NODE_ENV：NODE_ENV=production node app.js 设置 NODE_ENV 为 production 有如下优势：日志记录保持在最小 level更多的 caching 缓存级别来提高运行效率可以通过状态判读符来判断当前运行环境，执行不同指令：if (process.env.NODE_ENV === 'development') { console.log('running on development environment') } if (process.env.NODE_ENV === 'production') { console.log('running on production environment') } if (['production', 'staging'].indexOf(process.env.NODE_ENV) != -1) { console.log('running on production environment') } else { console.log('running on development environment') }上面第三个指令通过调用 array 的 indexOf 来获取某个字符串在数组中的索引号，如果不存在这个字符串则返回 -1。注意使用类型判断符=== 是为了保证类型和数值都匹配。error handling 错误处理node.js 中通过 exceptions 来处理 error 情况。可以通过 throw 创建一个 exception：const test = 123; throw test;当 JavaScript 执行到上面的 throw 时程序会立刻停止，然后控制权会交给最近的 exception handler 例外控制器。JavaScript 中 test 可以是任意的字符串，数字或者 object。但在 node.js 中只用来 throw Error objects。一个 error object 可以是 Error 的实例或者是 Error class 的 child class 的实例：throw new Error('error') class childError extends Error { } throw new childError('error')下面介绍几种 exception handler 处理器。第一种是通过 try/catch 声明来处理，在 try 中定义任意 exception 以及对应的 catch 块来响应：try { const a = 1; if (a == 0) { throw 'abc' } else { throw 123 } } catch (e) { console.log(e) }catch 的参数 e 就是对应 throw 的数据。如果在程序执行中，如果某个 throw 的 exception 没有被 catch，程序就会立刻 crash。可以通过监听 process 的 uncaughtException event 事件来解决这个问题：process.on('uncaughtException', err => { console.log('there was a uncaught error: ', err.message) process.exit(1) }) throw new Error('error')第二种是 promise 中的 error handler。使用 promise 可以将多个操作过程组合成链，在链的最后处理 error 情况，下面是一个简单示例：let a = 0; let b = 0; const doSomething1 = new Promise((resolve, reject) => { if (a == 0) resolve('resolve 1') else reject('reject 1') }) const doSomething2 = (data) => { return new Promise((resolve, reject) => { if (b == 0) resolve(data + ' : resolve 2') else reject('reject 2') }) } doSomething1 .then(doSomething2) .then(res => console.log(res)) .catch(rej => console.log(rej))当 doSomething1 为 resolve 时会执行 doSomething2()，为 reject 时会直接进入 catch 块。同样的 doSomething2 返回的 promise 为 reject 时也会进入 catch 块。此时通过输出的信息可以知道是在哪一个 promise 链响应了 exception。第三种是 async/await 的 error handling。使用 async/await 也是通过 try/catch 来处理 exception 情况的：let a = 1; const demo = new Promise((resolve, reject) => { if (a == 0) { setTimeout(resolve, 1000, 'resolve'); } else { reject('reject') } }) const test = async() => { try { const data = await demo console.log(data) } catch (error) { console.log(error) } } test() //output: //reject当 demo promise 为 reject 时会响应 test() 中的 catch 块。日志输出 object当我们在浏览器中使用 console.log() 输出某个 object 时，会得到一个很好的效果：点击箭头可以展开日志，完整地展示 object 的 properties：在 node.js 中和浏览器有所不同，它是将 object 的内容以字符串的形式输出到终端或者日志文件中。对于简单的 object 来说没有什么问题，但是对于包含超过 2 层嵌套以上的 object 来说 node.js 就会放弃输出所有内容而用一个 [Object] 占位符表示剩余内容：const obj = { name: 'joe', age: 35, person1: { name: 'Tony', age: 50, person2: { name: 'Albert', age: 21, person3: { name: 'Peter', age: 23 } } } } console.log(obj)输出结果为：{ name: 'joe', age: 35, person1: { name: 'Tony', age: 50, person2: { name: 'Albert', age: 21, person3: [Object] } } }可以看到第三层的内容已经用 [Object] 取代。强制要求输出 object 所有内容的最好的方法就是使用 JSON.stringify()：console.log(JSON.stringify(obj, undefined, 2))输出如下：{ "name": "joe", "age": 35, "person1": { "name": "Tony", "age": 50, "person2": { "name": "Albert", "age": 21, "person3": { "name": "Peter", "age": 23 } } } }第一个参数定义需要输出的 object，第二个参数是定义一个 function 用来转换字符串内容也可以不定义，第三个是定义缩进量。
Streams 是驱动 node.js 程序的核心概念。它提供了处理对文件的读写，网络传输，或者其他端到端的数据交换的更加高效的方式。streams 流并不是 node.js 首先引入的概念，unix 操作系统在很久之前就在使用了，一个程序可以通过 pipe 管道操作符 | 来传递 streams 流给其他程序。下面的示例是在 Linux 中，通过 pipe 管道将 cat 读取的文件数据传递给 grep 进行过滤，test.txt 文件内容如下：aaa bbb bbb ccc aaa ccc$ cat test.txt | grep aaa aaa bbb aaa cccstream 优点在传统方法中，当程序读取一个文件内容时会先将文件内容全部读取到内存中，然后再去使用它。而使用 stream 时会一段段的读取文件内容并进行处理，而不需要整体读取到内存。node.js 的 stream 模块是构建所有 streaming API 的底层。所有的 streams 流都是 eventEmitter 的实例。每种类型的 stream 都有各自的 event 事件，在数据流变化时触发对应事件。相比于其他数据处理方法，streams 有两个优势：内存效率：在使用数据前不需要将大量数据存入内存中。时间效率：能够更快的进入数据处理阶段，在获取到文件后能够很快的使用它而不需要等待它全部加载完毕。使用方法下面我们从一个示例来说明 stream 的使用方法。我们建立一个 http server，当收到请求时读取一个本地文件的内容并作为 response 发送给客户端。首先传统实现方式如下：const http = require('http') const fs = require('fs') const hostname = '127.0.0.1' const port = 3000 const server = http.createServer((req, res) => { fs.readFile(__dirname + '/test.txt', (err, data) => { if (err) { console.log('read error') } res.end(data) }) }) server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`) })以上方式在收到请求后，通过 readFile() 读取文件内容到内存中，当成功后会调用 callback 通过 res.end(data) 发送读取的数据给客户端。如果文件较大时以上过程会花费一些时间。__dirname 返回当前执行文件的路径。下面介绍使用 stream 实现上面的过程：const http = require('http') const fs = require('fs') const hostname = '127.0.0.1' const port = 3000 const server = http.createServer((req, res) => { const stream = fs.createReadStream(__dirname + '/test.txt') stream.pipe(res) }) server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`) })通过 fs.createReadStream() 返回一个文件的 stream。不同于第一种，以上方式会在 stream 中只要有了 data chunk 块就立刻作为 response 数据传输给客户端。这样就会一边读取文件一边传输数据。pipe()以上示例中调用 stream 的 pipe() method。它的功能是建立一个 source stream 源流到 destination stream 目标流的 pipe 管道。这样文件的 stream 流通向了 http response。pipe() 的返回是 destination stream 目标流，这样就可以很方便的链接多个 pipe：src.pipe(dest1).pipe(dest2)以下写法和上面示例效果一样：src.pipe(dest1) dest1.pipe(dest2)基于 stream 的 node.js API由于 stream 的巨大优势，很多 node.js 核心模块提供了 stream 的原生支持，以下是常用的部分：process.stdin 返回一个链接到 stdin 的 streamprocess.stdout 返回一个链接到 stdout 的 streamprocess.stderr 返回一个链接到 stderr 的 streamfs.createReadStream() 创建一个文件的可读的 streamfs.createWriteStream() 创建一个文件的可写的 streamnet.connect() 初始化一个基于 stream 的连接http.request() 返回一个 http.ClientRequest 实例，它是一个可写的 streamstreams 的类型有四个 streams 的 calsses：Readable: 一个可以作为管道源头的 stream，不能作为管道的目标也就是不能写入数据Writable: 一个可以作为管道目标的 stream，不能作为管道源头也就是不能从中获取数据Duplex: 一个既可作为管道源头也可以作为目标的 streamTransform: 类似于 Duplex创建 readable stream从 stream 模块定义一个可读的 stream 然后通过定义 readable._read() method 内容完成初始化：const Stream = require('stream') const readableStream = new Stream.Readable() readableStream._read = () => {}初始化也可以这样写：const readableStream = new Stream.Readable({ read() {} })以上就创建了一个可读的 stream，可以给其传输数据：readableStream.push('hi') readableStream.push('hello')可以将这个 stream 和一个可写的 stream 之间建立管道：readableStream.pipe(process.stdout) 创建 writable stream通过实例化一个 Writable object 然后通过定义 _write() method 内容完成初始化：const Stream = require('stream') const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, callback) => { console.log(chunk.toString()) callback() }接收到的数据块传入 chunk，encoding 定义数据类型，callback function 是当一个 chunk 数据块传输完成后调用，此 callback 可在 writeableStream.write 内定义，一般可以是 error 处理，下面会介绍。可以将定义的可写 stream 同一个可读 stream pipe 管道连接：process.stdin.pipe(writeableStream) 这样就可以在 stdin 和 writeableStream 间建立 pipe，此时在 stdin 输入数据就会立刻将数据显示在终端。从可读的 stream 读取数据下面的示例中，我们建立一个可读的 stream 和一个 可写的 stream，并建立 pipe 管道连接：const Stream = require('stream') const readableStream = new Stream.Readable() readableStream._read = () => {} const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, callback) => { console.log(chunk.toString()) callback() } readableStream.pipe(writeableStream) readableStream.push('hi\n') readableStream.push('hello\n')也通过 readable event 事件来处理可读 stream。当 stream 中有准备好的数据块时会触发 readable event：readableStream.on('readable', () => { console.log(readableStream.read().toString()) })给可写的 stream 写入数据使用 write method 来写入数据：writeableStream.write('hello world\n') 可以定义写入数据的编码格式和 callback function：writeableStream.write('hello world\n', 'utf-8', err => {console.log(err)}) 实际中 callback function 是否会执行要看 stream 初始化中在 writeableStream._write 是否调用了 callback。如果数据写入已经完成，可以使用 end method 来告诉可写的 stream：const Stream = require('stream') const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, next) => { console.log(chunk.toString()) next() } process.stdin.pipe(writeableStream) writeableStream.write('hello world\n') writeableStream.end()以上示例中如果在最后一句不调用 end method 则 writeableStream 会持续保持接收来自 stdin 的数据状态。创建 transform stream和可写的 stream 创建方式类似，可读也可写：const Stream = require('stream') const transformStream = new Stream.Transform() transformStream._transform = (chunk, encoding, callback) => { console.log('transform' + chunk.toString()) transformStream.push(chunk) callback() } const writeableStream = new Stream.Writable() writeableStream._write = (chunk, encoding, callback) => { console.log('write' + chunk.toString()) callback() } process.stdin.pipe(transformStream).pipe(writeableStream)在 transformStream._transform 定义了当 transform stream 接收到数据后通过 transformStream.push 将数据发到 readable stream 中，这样其他 stream 就可以读取到它接收到的数据了。更多 stream 使用方法参考：https://nodejs.org/api/stream.html
一个 buffer 缓存就是一个内存空间。JavaScript 开发者相较于 c++/go 等语言的开发者对内存的理解比较少。它表示在 JavaScript V8 engine 之外的分配的一块 fixed-size 固定大小的内存。可以想象 buffer 就是一个 integer 整型数组，每个元素代表一个 byte 的数据。node.js 中 buffer 通过 Buffer class 实现。为何需要 buffer引入 Buffer 是为了帮助开发者处理 binary 二进制数据。传统的 ecosystem 只可以处理 strings 字符串数据。Buffer 和 streams 是紧密关联的，当 stream processor 流处理器接收数据的速度大于其处理速度时，就会先将数据放在 buffer 中。可以想象下，在我们看在线视频时，在网络较好的情况下进度条中可以看到视频下载的进度是超过当前播放进度的，此时浏览器就会先 buffer 缓存数据后续按时间顺序播放出来。创建 buffer可以通过 Buffer.from(), Buffer.alloc() 和 Buffer.allocUnsafe() methods 来创建一个 buffer，下面依次介绍。使用 Buffer.from() 可以从 array 数组创建 buffer：const buf1 = Buffer.from('hey!') 使用 Buffer.from(string[, encoding]) 可以设置 string 的编码类型，Default: utf8：const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex') 也可以根据另一个 buffer：const buf3 = Buffer.from(buf1) 通过 Buffer.alloc() 或 Buffer.allocUnsafe() 来初始化一个指定大小的 buffer，单位为 byte 字节：const buf4 = Buffer.alloc(1024) const buf5 = Buffer.allocUnsafe(1024) 以上会创建一个 1kb 大小的 buffer。通过 alloc 创建的 buffer 会初始化其数据为 0，而 allocUnsafe 创建的 buffer 内的数据会是 uninitiated 未初始化的，这就表示使用 allocUnsafe 比 alloc 创建速度更快。但是通过 allocUnsafe 创建的 buffer 内存空间会包含有以前的数据可能引起潜在问题。使用 buffer我们可以像访问数组一样访问 buffer 数据：const buf1 = Buffer.from('hey!') console.log(buf1[0]) console.log(buf1[1]) console.log(buf1[2]) //output: //104 //101 //121这些输出的数字就是代表 buffer 中每个字符的 Unicode Code，h => 104, e => 101, y => 121。使用 toString() method 可以将 buffer 输出为字符：console.log(buf1) console.log(buf1.toString()) //output: //<Buffer 68 65 79 21> //hey!buf1.length 可以读取 buffer 长度：console.log(buf1.length) //output: //4也可以使用循环 iterate buffer 的数据：for (const iterator of buf1) { console.log(iterator) } //output: 104 101 121 33通过 write() method 可以给 buffer 写入数据：const buf6 = Buffer.alloc(5) buf6.write('hello')也可以像数组一样修改某个元素的数据：const buf6 = Buffer.alloc(5) buf6.write('hello') buf6[0] = 111 console.log(buf6.toString()) //output: //oello可以看到需要修改字符对应的 Unicode Code。使用 copy() method 可以复制 buffer 数据到其他 buffer：const buf1 = Buffer.from('hey!') const buf7 = Buffer.alloc(4) buf1.copy(buf7) console.log(buf7.toString()) //output: //hey!注意是源 buffer 掉用 copy method 来复制数据到目标 buffer。默认情况下会复制 buffer 的整个数据， copy method 可以设置其他三个参数来自定义目标 buffer 起始接收数据位置，源 buffer 起始复制数据位置，以及复制的新 buffer 的长度：const buf8 = Buffer.alloc(2) buf1.copy(buf8, 0, 0, 2) console.log(buf8.toString()) //output: //he可以使用 slice method 来选取 buffer 的一个片段，它和 copy 的不同是 slice 获取的 buffer 片段仍然和源 buffer 相关联，修改源 buffer 会改变片段中的 buffer 数据。slice 的第一个参数定义起始位置，第二个参数是选项，可以定义终止位置：const buf1 = Buffer.from('hey!') const slice = buf1.slice(0, 2) console.log(slice.toString()) buf1[0] = 111 console.log(slice.toString()) //output: //he //oe以上就是 Buffer 的简单使用方法。
http 模块是 node.js 处理 networking 网络的关键模块。在之前的章节我介绍了使用 http 模块建立 server 以及发起 GET/POST 请求的方法，可以参考：https://blog.niekun.net/archives/2137.htmlhttp 模块集成于 node.js 核心无需单独安装，使用下面命令引入模块：const http = require('http') 模块提供了很多 properties，methods 和 classes。Properties 属性http.METHODShttp.METHODS property 返回 http 所有的可用 method 列表：> console.log(require('http').METHODS) [ 'ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE', 'GET', 'HEAD', 'LINK', 'LOCK', 'M-SEARCH', 'MERGE', 'MKACTIVITY', 'MKCALENDAR', 'MKCOL', 'MOVE', 'NOTIFY', 'OPTIONS', 'PATCH', 'POST', 'PRI', 'PROPFIND', 'PROPPATCH', 'PURGE', 'PUT', 'REBIND', 'REPORT', 'SEARCH', 'SOURCE', 'SUBSCRIBE', 'TRACE', 'UNBIND', 'UNLINK', 'UNLOCK', 'UNSUBSCRIBE' ]http.STATUS_CODEShttp.STATUS_CODES 返回所有的 http 状态码及描述的 list 列表：> console.log(require('http').STATUS_CODES) { '100': 'Continue', '101': 'Switching Protocols', '102': 'Processing', '103': 'Early Hints', '200': 'OK', '201': 'Created', '202': 'Accepted', '203': 'Non-Authoritative Information', '204': 'No Content', '205': 'Reset Content', '206': 'Partial Content', '207': 'Multi-Status', '208': 'Already Reported', '226': 'IM Used', '300': 'Multiple Choices', '301': 'Moved Permanently', '302': 'Found', '303': 'See Other', '304': 'Not Modified', '305': 'Use Proxy', '307': 'Temporary Redirect', '308': 'Permanent Redirect', '400': 'Bad Request', '401': 'Unauthorized', '402': 'Payment Required', '403': 'Forbidden', '404': 'Not Found', '405': 'Method Not Allowed', '406': 'Not Acceptable', '407': 'Proxy Authentication Required', '408': 'Request Timeout', '409': 'Conflict', '410': 'Gone', '411': 'Length Required', '412': 'Precondition Failed', '413': 'Payload Too Large', '414': 'URI Too Long', '415': 'Unsupported Media Type', '416': 'Range Not Satisfiable', '417': 'Expectation Failed', '418': "I'm a Teapot", '421': 'Misdirected Request', '422': 'Unprocessable Entity', '423': 'Locked', '424': 'Failed Dependency', '425': 'Too Early', '426': 'Upgrade Required', '428': 'Precondition Required', '429': 'Too Many Requests', '431': 'Request Header Fields Too Large', '451': 'Unavailable For Legal Reasons', '500': 'Internal Server Error', '501': 'Not Implemented', '502': 'Bad Gateway', '503': 'Service Unavailable', '504': 'Gateway Timeout', '505': 'HTTP Version Not Supported', '506': 'Variant Also Negotiates', '507': 'Insufficient Storage', '508': 'Loop Detected', '509': 'Bandwidth Limit Exceeded', '510': 'Not Extended', '511': 'Network Authentication Required' }http.globalAgenthttp.globalAgent 指向 http.Agent class 的一个 instance 实例，也就是一个 Agent object。这个 Agent object 用来管理 server 同 http 客户端链接的持续连接和链接复用，这也是 node.js 网络服务的关键点。> console.log(require('http').globalAgent) Agent { _events: [Object: null prototype] { free: [Function (anonymous)], newListener: [Function: maybeEnableKeylog] }, _eventsCount: 2, _maxListeners: undefined, defaultPort: 80, protocol: 'http:', options: { path: null }, requests: {}, sockets: {}, freeSockets: {}, keepAliveMsecs: 1000, keepAlive: false, maxSockets: Infinity, maxFreeSockets: 256, scheduling: 'lifo', maxTotalSockets: Infinity, totalSocketCount: 0, [Symbol(kCapture)]: false }methods 功能http.createServer()http.createServer() 返回一个 http.Server class 实例。const server = http.createServer((req, res) => { //handle every single request with this callback })http.request()http.request() 对服务器发起一个 http 请求。返回一个 http.ClientRequest class 实例。const options = { hostname: 'localhost', port: 3000, path: '/', method: 'GET' } const req = https.request(options, res => { console.log(`statusCode is：${res.statusCode}`); res.on('data', d => { process.stdout.write(d) }) }) req.on('error', err => console.log(err)) req.end()http.get()http.get() 同 http.request() 类似，但会自动将 http method 设置为 GET，且自动调用 req.end()。const https = require('http') const options = { hostname: 'localhost', port: 3000, path: '/' } const req = https.get(options, res => { console.log(`statusCode is：${res.statusCode}`); res.on('data', d => { process.stdout.write(d) }) }) req.on('error', err => console.log(err))Classes 类http 模块提供 5 个 calsses：http.Agenthttp.ClientRequesthttp.Serverhttp.ServerResponsehttp.IncomingMessagehttp.Agentnode.js 会创建一个全局的 http.Agent instance 实例来管理 server 同 http 客户端链接的持续连接和链接复用，这个 object 可以确保一个客户端对服务端发起的一系列请求是按队列排序的，且使用单独的 socket 接口。同样 agent object 管理着一个 sockets 池，这是影响性能的关键因素。http.ClientRequest当 http.request() 或 http.get() 被调用时会创建一个 http.ClientRequest object。当收到来自服务器的 response 时，response event 会被触发，http.request() 内定义的 callback 会作为 response 的响应被调用，同时将 http.IncomingMessage 的实例作为传入参数，其中包含了 response 数据。response 数据有两种方式读取，第一种是通过 response.read() method，第二种是在 response 的 callback function 中通过监听 data event 来获取 stream 中的数据。http.Server当通过 http.createServer() 创建一个 server 时会返回 http.Server 实例。 http.Server 实例需要使用它的以下 method：close() 停止 server 接收新的链接listen() 启动 server 并开始监听请求http.createServer() 的 callback 监听 request event 并响应。http.ServerResponse由 http.Server object 创建，作为 request event 的第二个参数传入，代码中作为 callback 的 res 参数使用：const server = http.createServer((req, res) => { //res is an http.ServerResponse object })end() 是 http.ServerResponse object 必须被使用的 method，用来表示 response 信息已经完整，可以结束这个 response，同时将 response 发送出去。下面的 method 可以用来处理 http headers：getHeaderNames() 获取当前 http headers 名称的列表getHeaders() 获取当前 http headers 副本setHeader('headername', value) 设置一个 http headergetHeader('headername') 获取当前的一个 http header 的设置removeHeader('headername') 删除一个 http headerhasHeader('headername') 如果某个 http header 有被设置则返回 trueheadersSent() 如果 http headers 已经发送给了客户端则返回 true服务端编辑好 headers 条目后可以通过 response.writeHead() method 发送给客户端，第一个参数是 statusCode。通过 response.write() 在 response body 中发送数据给客户端，它会将缓冲区的数据发送到 http response stream。如果还没有使用 response.writeHead() 发送 headers 则会先发送 headers，包含 statusCode 和 message，它们可以通过下面语法设置：response.statusCode = 500 response.statusMessage = 'Internal Server Error'http.IncomingMessagehttp.IncomingMessage 可以在以下两个地方创建：http.Server 当监听 request eventhttp.ClientRequest 当监听 response event它可以用来访问 response 数据：通过 statusCode 和 statusMessage 读取状态信息通过 headers method 或 rawHeaders method 读取 headers 信息通过 method method 获取可用 http method通过 httpVersion method 获取 http 版本信息通过 url method 获取 URL 信息通过 socket method 获取底层 socket 信息在 http.IncomingMessage 生效一个可读的 stream 接口后，数据可在 stream 中读取。
在之前介绍 event loop 的章节，我们简单介绍了 events 模块的使用，它可以设置并监听某个信号并在信号触发时响应 callback function。events 模块提供了 EventEmitter class 它是处理 events 的关键工具。引入及初始化 events 模块如下：const EventEmitter = require('events') const eventEmitter = new EventEmitter()每个 event listener 事件监听器都是互相独立的，同时它们使用如下 events：newListener 当一个 event 被添加removeListener 当一个 event 被删除下面介绍常用的一些 method。eventEmitter.addListener()eventEmitter.addListener() 和 eventEmitter.on() 功能相同。可以给某个 event 添加一个 listener，语法如下：eventEmitter.addListener('test', () => {}) eventEmitter.emit()eventEmitter.emit() 用来触发一个信号，信号触发后，对应信号的 event listener 会响应 callback function：eventEmitter.addListener('test', () => { console.log('test emited') }) eventEmitter.emit('test') //output: //test emitedeventEmitter.eventNames()eventEmitter.eventNames() 会返回当前 EventEmitter object 内注册已的 events 为一个字符串数组：eventEmitter.addListener('test1', () => {}) eventEmitter.addListener('test2', () => {}) console.log(eventEmitter.eventNames()) //output: //[ 'test1', 'test2' ]eventEmitter.getMaxListeners()eventEmitter.getMaxListeners() 返回当前 EventEmitter object 能添加的 event listener 最大数量。默认为 10 个，可以通过 setMaxListeners() 修改。eventEmitter.listenerCount()eventEmitter.listenerCount() 返回某个 event 定义的 listeners 数量：eventEmitter.addListener('test4', () => { console.log('a') }) eventEmitter.addListener('test4', () => { console.log('b') }) console.log(eventEmitter.listenerCount('test4')) //output: //2eventEmitter.listeners()eventEmitter.listeners() 返回某个 event 所有的 listeners 为一个数组：const f1 = () => {} const f2 = () => {} eventEmitter.addListener('test5', f1) eventEmitter.addListener('test5', f2) console.log(eventEmitter.listeners('test5')) //output: //[ [Function: f1], [Function: f2] ]eventEmitter.off()eventEmitter.off() 和 eventEmitter.removeListener() 功能相同。可以删除一个 event 的某个 listener，需要指定 event 名称和 listener callback 名称：const f3 = () => {} eventEmitter.addListener('test6', f3) eventEmitter.off('test6', f3)eventEmitter.on()eventEmitter.on() 同 eventEmitter.addListener() 功能相同。可以给某个 event 添加一个 listener：eventEmitter.on('test6', () => {})eventEmitter.once()eventEmitter.once() 监听一个 event 且其 callback function 只能被执行一次：eventEmitter.once('test7', () => console.log('once show once')) eventEmitter.emit('test7') eventEmitter.emit('test7') //output: //once show onceeventEmitter.prependListener()当通过 eventEmitter.on() 添加 listener 时，会按照添加顺序排列在此 event 队列中，当对应 event 触发时，会按照排列顺序以此执行 callback。使用 eventEmitter.prependListener() 添加的 listener 会添加到队列的最前面，触发时最先被执行：eventEmitter.on('test8', () => console.log('first listener')) eventEmitter.prependListener('test8', () => console.log('second listener')) eventEmitter.emit('test8') //output: //second listener //first listenereventEmitter.prependOnceListener()eventEmitter.prependOnceListener() 同 eventEmitter.on() 的区别和上面类似，会将 listener 添加到对应 event listeners 队列的最前面，然后最先被执行且只能响应一次：eventEmitter.once('test9', () => console.log('first listener')) eventEmitter.prependOnceListener('test9', () => console.log('second listener')) eventEmitter.emit('test9') eventEmitter.emit('test9') //output: //second listener //first listenereventEmitter.removeAllListeners()eventEmitter.removeAllListeners() 可以删除指定 event 的所有 listeners：eventEmitter.removeAllListeners('test9')eventEmitter.removeListener()eventEmitter.removeListener() 可以删除 event 的指定 listener。需要将 listener 的 callback function 定义为变量格式然后再调用，这样就可以在此 reference 它：const f1 = () => {} const f2 = () => {} eventEmitter.addListener('test5', f1) eventEmitter.addListener('test5', f2) eventEmitter.removeListener('test5', f1)eventEmitter.setMaxListeners()eventEmitter.setMaxListeners() 可以定义 EventEmitter object 最大可以添加的 listeners 数量：eventEmitter.setMaxListeners(20) console.log(eventEmitter.getMaxListeners()) //output: //20以上就是 events 模块最常用功能的介绍。
os 模块可以用来获取操作系统的底层信息和系统上运行的程序，以及和系统交互。它集成在 node.js 中，只需要引入即可：const os = require('os') 这里先介绍一些在处理文件时有用的 properties：os.EOL 会返回 line delimiter 行定界符，\n on Linux and macOS, and \r\n on Windowsos.constants.signals 提供处理进程的信号常数，如：SIGHUP, SIGKILLos.constants.errno 提供报告 error 的相关常数，如：EADDRINUSE, EOVERFLOW完整的 signals 信号列表参考：https://nodejs.org/api/os.html#os_signal_constants下面介绍 os 模块常用的 method。os.arch()os.arch() 会返回底层架构的字符串，如：arm, x64, arm64：console.log(os.arch()) //output: x64os.cpus()os.cpus() 会返回系统的 cpu 信息：console.log(os.cpus()) //output: [ { model: 'Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz', speed: 2591, times: { user: 1871810, nice: 17880, sys: 1451160, idle: 81134010, irq: 0 } }, { model: 'Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz', speed: 2591, times: { user: 1838130, nice: 12780, sys: 1532060, idle: 81234660, irq: 0 } } ]os.endianness()根据 node.js 是通过 Big Endian 或 Little Endian 大尾数或小尾数编译的，os.endianness() 返回 BE 或 LE：console.log(os.endianness()) //output: LE关于 Big Endian 或 Little Endian 参考：https://en.wikipedia.org/wiki/Endiannessos.freemem()os.freemem() 返回系统可用内存，以字节为单位：console.log(os.freemem()) //output: 574537728os.homedir()os.homedir() 返回系统当前用户的 home 目录路径：console.log(os.homedir()) //output: /home/marcoos.hostname()os.hostname() 返回 hostname 信息：console.log(os.hostname()) //output: marco-virtual-machineos.loadavg()os.loadavg() 返回系统计算的平均加载时间，此 method 只在 Linux/macOS 下有意义：console.log(os.loadavg()) //output: [ 0.08, 0.17, 0.13 ]os.networkInterfaces()os.networkInterfaces() 返回系统的网卡信息：console.log(os.networkInterfaces()) //output: { lo0: [ { address: '127.0.0.1', netmask: '255.0.0.0', family: 'IPv4', mac: 'fe:82:00:00:00:00', internal: true }, { address: '::1', netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', family: 'IPv6', mac: 'fe:82:00:00:00:00', scopeid: 0, internal: true }, { address: 'fe80::1', netmask: 'ffff:ffff:ffff:ffff::', family: 'IPv6', mac: 'fe:82:00:00:00:00', scopeid: 1, internal: true } ], en1: [ { address: 'fe82::9b:8282:d7e6:496e', netmask: 'ffff:ffff:ffff:ffff::', family: 'IPv6', mac: '06:00:00:02:0e:00', scopeid: 5, internal: false }, { address: '192.168.1.38', netmask: '255.255.255.0', family: 'IPv4', mac: '06:00:00:02:0e:00', internal: false } ], utun0: [ { address: 'fe80::2513:72bc:f405:61d0', netmask: 'ffff:ffff:ffff:ffff::', family: 'IPv6', mac: 'fe:80:00:20:00:00', scopeid: 8, internal: false } ] }os.platform()os.platform() 返回当前安装的 node.js 的编译平台，可能的返回值为：darwinfreebsdlinuxopenbsdwin32...moreos.release()os.release() 返回当前系统的版本号信息：console.log(os.release()) //output: 5.8.0-41-genericos.tmpdir()os.tmpdir() 返回系统的临时文件夹路径：console.log(os.tmpdir()) //output: /tmpos.totalmem()os.totalmem() 返回系统的总内存数量，单位为 byte 字节：console.log(os.totalmem()/1024/1024) //output: 1958.45703125os.type()os.type() 返回当前操作系统类型，返回值可以有：LinuxDarwin on macOSWindows_NT on Windowsos.uptime()os.uptime() 返回系统从上次开机来已运行的总时间，单位为秒：console.log(os.uptime()/3600) //output: 24.260555555555555os.userInfo()os.userInfo() 返回一个 object 包含当前的 username, uid, gid, shell, 和 homedir：console.log(os.userInfo()) //output: { uid: 1000, gid: 1000, username: 'marco', homedir: '/home/marco', shell: '/bin/bash' }以上就是 os 模块的简单使用方法。
上一章节我们了解了如果访问文件系统。使用到了 fs 和 path 模块的部分功能，下面我们详细了解这两个模块可以实现的功能。fs module通过以上几个使用场景可以看到 fs 模块提供了很多有用的功能来对 file system 进行访问和交互。并且他是集成在 node.js core 中的，并不需要手动安装：const fs = require('fs') 通过以上方式引入 fs 模块后，我们就可以使用它的各种功能了。包括：fs.access(): 检查文件是否存在以及 node.js 是否有权限访问fs.appendFile(): 给一个文件附加数据，如果文件不存在则会创建它fs.chmod(): 修改文件的权限fs.chown(): 修改文件的 owner 和 group 设置fs.close(): 关闭已经打开的文件的 descriptorfs.copyFile(): 复制一个文件fs.createReadStream(): 创建一个可读的文件 streamfs.createWriteStream(): 创建一个可写的文件 streamfs.link(): 创建一个文件的硬链接fs.mkdir(): 新建文件夹fs.mkdtemp(): 创建一个临时文件夹fs.open(): 打开一个文件的 descriptor，可定义打开编码类型fs.readdir(): 读取一个目录下的内容fs.readFile(): 读取一个文件的内容fs.readlink(): 读取一个软连接的值fs.realpath(): resolve 查询一个相对路径文件的绝对路径fs.rename(): 重命名文件或文件夹fs.rmdir(): 删除一个文件夹fs.stat(): 返回一个文件的状态信息fs.symlink(): 创建一个文件的软链接fs.truncate(): 将一个文件分割成指定长度大小fs.unlink(): 删除一个文件或一个软链接fs.unwatchFile(): 停止监测对一个文件的修改fs.utimes(): 修改一个文件的时间戳fs.watchFile(): 开始监测对一个文件的修改fs.writeFile(): 对一个文件写入数据fs 模块的一个特性是它的以上所有 method 默认都是 asynchronous 异步的，给 method 后加上 Sync 就可以使用同步模式，例如：fs.rename() -> fs.renameSync()fs.write() -> fs.writeSync()同步模式下通过使用 try/catch 块来处理 error 情况。同步模式下程序会在处理文件过程中出现进程阻塞直到处理完成，在编程中可以根据需要灵活运用。path modulepath 模块也提供了很多实用的功能来访问文件系统以及交互。同样的它也是集成在 node.js core 中而不需要单独安装。const path = require('path') 通过 path.sep 可以获取当前系统的 path separator 路径分隔符：\ on Windows, and / on Linux/macOS。通过 path.delimiter 可以获取当前系统的 path delimiter 路径定界符：; on Windows, and : on Linux / macOS。例如在 Linux 下：console.log(process.env.PATH); // Prints: '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin' process.env.PATH.split(path.delimiter); // Returns: ['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin']在 Windows 下：console.log(process.env.PATH); // Prints: 'C:\Windows\system32;C:\Windows;C:\Program Files\node\' process.env.PATH.split(path.delimiter); // Returns ['C:\\Windows\\system32', 'C:\\Windows', 'C:\\Program Files\\node\\']下面是 path 的常用 method 介绍。path.basename()返回路径上的最后一部分，通过第二个参数可以过滤掉扩展名：console.log(path.basename('/home/marco/dir')) console.log(path.basename('/home/marco/file.txt')) console.log(path.basename('/home/marco/file.txt', '.txt')) //output: dir file.txt filepath.dirname()返回路径上的文件夹部分：console.log(path.dirname('/home/marco/file')) //output: /home/marcopath.extname()返回文件的扩展名：console.log(path.extname('/home/marco/file.txt')) //output: .txtpath.isAbsolute()返回路径是否是绝对路径：console.log(path.isAbsolute('/home/marco')) console.log(path.isAbsolute('./marco')) //output: true falsepath.join()将多个部分合并为一个路径：const name = 'marco'; console.log(path.join('/home/' + name + '/file')) //output: /home/marco/filepath.normalize()将一个包含相对标记符的路径转换为常规绝对路径：console.log(path.normalize('/home/marco/../test')) //output: /home/testpath.parse()将路径拆分为一个 object，包含下面的 properties：root: 根目录dir: 从根目录开始的文件夹部分base: 文件名和扩展名name: 文件名不包含扩展名ext: 扩展名console.log(path.parse('/home/marco/test/file.txt')) //output: { root: '/', dir: '/home/marco/test', base: 'file.txt', ext: '.txt', name: 'file' }path.relative()需要传入两个路径作为参数，第一个路径作为 base 基础路径，返回第二个路径相对于第一个路径的相对路径：console.log(path.relative('/home/marco', '/home/marco/test/file.txt')) //output: test/file.txtpath.resolve()计算一个相对路径的绝对路径，基于当前程序执行的路径：console.log(path.resolve('file.txt')) //output: /mnt/hgfs/Development/node.js/filesystem/file.txt也可以设置一个 base 路径，将会附加到相对路径中：console.log(path.resolve('tmp/test', 'file.txt')) //output: /mnt/hgfs/Development/node.js/filesystem/tmp/test/file.txt如果第一个 base 路径是绝对路径，则系统会认为这就是个绝对路径：console.log(path.resolve('/tmp/test', 'file.txt')) //output: /tmp/test/file.txt注意 path 模块对路径的处理不会判断这个路径是否真实存在，它只会按照指令处理路径这个字符串数据。
File descriptor 文件描述器当需要访问文件系统里的某个文件时，需要首先得到这个文件的 file descriptor 文件描述器。一个 file descriptor 就是通过 fs 模块的 open() method 打开对应文件的返回数据。它的结构如下：const fs = require('fs') fs.open('./test.txt', 'r', (err, data) => { if (err) { console.log('open fail') return } console.log('open success') })如果文件打开成功，callback 中的 data 数据就是一个 file descriptor。注意并不是文件的内容。这种方式是异步处理的，文件打开过程中系统会执行其他任务。在上面的示例中，fs.open() 的第三个参数使用了 'f' 标记，它的意思就是以只读模式打开文件。我们可以通过使用不同的标记来以不同的方式打开文件：r 只读模式，文件不存在会报错a 只写模式，streaming 流定位到文件结尾位置，如果文件不存在则创建文件r+ 读写模式，文件不存在会报错w+ 读写模式，streaming 流定位到文件起始位置，如果文件不存在则创建文件a+ 读写模式，streaming 流定位到文件结尾位置，如果文件不存在则创建文件更多 flag 的使用参考：https://nodejs.org/api/fs.html#fs_file_system_flags也可以使用 fs.openSync() method 打开文件，其返回值为 file descriptor 而不是通过 callback 的方式：try { const data = fs.openSync('./test.txt', 'r') console.log('open success') } catch (error) { console.log('open fail') }这种方式是同步模式的，程序会等待接收到 file descriptor 或者 error 后才执行后面的指令。File stats 文件信息每个文件都包含独自的属性信息，可以通过 node.js 查看。通常我们使用 fs 模块的 stat() method。以下是一个简单示例：const fs = require('fs') fs.stat('./test.txt', (err, stats) => { if (err) { console.log('read fail') return } console.log(stats) })同样的，node.js 也提供了同步模式 method，在读取 stats 过程中会阻塞进程知道读取结束：try { const stats = fs.statSync('./test.txt') console.log(stats) } catch (error) { console.log(error) }文件信息存储在 stats 变量中，可以读取其中的需要的信息，常用的有以下：是否是文件或文件夹：stats.isFile()，stats.isDirectory()是否是一个链接文件：stats.isSymbolicLink()文件大小(单位为字节)：stats.sizeFile Path 文件路径每个文件在系统中都有一个路径。在 Linux 中路径格式如：/users/joe/file.txt，在 Windows 中路径格式如：C:\users\joe\file.txt。在程序中引用路径时需要特别注意路径的格式。node.js 中可以使用 path 模块来处理文件路径相关数据。如果给定一个路径，可以提取其相关数据，如：path.dirname: 获取文件的上级文件夹路径path.basename: 获取文件名部分path.extname: 获取文件扩展名const path = require('path') const file = '/home/marco/file.txt' console.log(path.dirname(file)) console.log(path.basename(file)) console.log(path.extname(file)) //output: /home/marco file.txt .txt可以获取文件名不包含扩展名部分，通过给 path.basename() 设置第二个参数：console.log(path.basename(file, path.extname(file))) //output: file可以组合多个部分为一个 path：const dir = 'home/marco/' p = path.join('/' + dir + 'test.txt') console.log(p) //output: /home/marco/test.txt使用 path.resolve() 可以从一个相对路径获取到绝对路径：p = path.resolve('file.txt') console.log(p) p = path.resolve('./test/file.txt') console.log(p) //OUTPUT: /mnt/hgfs/Development/node.js/filesystem/file.txt /mnt/hgfs/Development/node.js/filesystem/test/file.txt以上示例中，会将程序当前执行路径作为绝对路径添加给后面定义的文件相对路径。也可以将文件的相对目录单独定义，会自动组合它们：p = path.resolve('tmp', 'file.txt') console.log(p) //output: /mnt/hgfs/Development/node.js/filesystem/tmp/file.txt如果在路径前加斜杠/ 表明这就是一个绝对路径：p = path.resolve('/tmp', 'file.txt') console.log(p) //output: /tmp/file.txt如果路径中包含相对关系标记符如: .., //等，可以使用 path.normalize() 得到常规形式的路径：p = path.normalize('/home/../test/file.txt') console.log(p) //output: /test/file.txtpath.resolve 和 path.normalize 都不会检查路径是否真实存在，它们仅仅是根据提供的数据计算路径结果。reading file 读取文件最简单的方式读取文件内容就是通过 fs.readFile() method，需要给它传入文件路径，编码格式，callback function:const fs = require('fs') fs.readFile('./test.txt', 'utf-8', (err, data) => { if (err) { console.log('read fail') return } console.log(data) })或者也可以使用同步模式的 fs.readFileSync()：try { data = fs.readFileSync('./test.txt', 'utf-8') console.log(data) } catch (error) { console.log('read error') }fs.readFile() 和 fs.readFileSync() 都会先将文件内容读取到内存中，然后返回数据。这就意味着读取大文件会影响系统内存的占用量，所以一个比较好的选择是使用 stream 流读取文件内容。后续章节会介绍 stream 模块。writing file 文件写入最简单的写入文件的方法就是使用 fs.writeFile() method。示例如下：const fs = require('fs') const content = 'some new words' fs.writeFile('./test.txt', content, err => { if (err) { console.log('write error') return } console.log('write success') })或者使用同步模式的版本 fs.writeFileSync()：try { fs.writeFileSync('./test.txt', content) console.log('write success') } catch (error) { console.log('write error') }默认情况下，如果这个文件已经存在，API 会替换掉这个文件中已有的内容。我们可以通过定义 flag 来修改这个设置：fs.writeFile('./test.txt', content, {flag: 'a+'}, err => { if (err) { console.log('write error') return } console.log('write success') })flag 的定义和上面的 fs.open 中定义的 flag 一样，可以参考设置。append to a file 给文件添加内容给已有文件附加内容更加方便的方法是使用 fs.appendFile()和 fs.appendFileSync()，例如：const fs = require('fs') content = '\nnew line\n' fs.appendFile('./test.txt', content, err => { if (err) { console.log('write error') return } console.log('write success') })上面了方法都会在将数据完全写入文件后在执行 callback，这种情况下使用 stream 是一个更好的方法。working with folder 文件夹操作node.js 的 fs 模块提供了很多实用的 method 来对文件夹进行操作。检查文件夹是否存在使用 fs.access() 可以检查文件夹是否存在，以及 node.js 是否有访问它的权限：const fs = require('fs') fs.access('./tmp', err => { console.log(err ? 'not exist' : 'exist') }) //output: //not exist创建新文件夹使用 fs.mkdir() 或 fs.mkdirSync() 创建新文件夹：const fs = require('fs') const fd = 'tmp' try { if (!fs.existsSync(fd)) { fs.mkdirSync(fd) console.log('create success') } } catch (error) { console.log('create fail') }上面使用了同步模式 method 创建文件夹，看起来更加直观。read content of a directory 读取目录内容使用 fs.readdir() 或 fs.readdirSync() 可以读取某个目录中的内容，包括其中的文件和文件夹。返回值为一个数组：const fs = require('fs') const p = '/usr' const d = fs.readdirSync(p) console.log(d)输出为：[ 'bin', 'games', 'include', 'lib', 'lib32', 'lib64', 'libexec', 'libx32', 'local', 'sbin', 'share', 'src' ]通过 path 模块的 join 或 resolve method 可以输出每个元素的绝对路径，修改以上示例：const fs = require('fs') const path = require('path') const p = '/usr' const d = fs.readdirSync(p).map(f => { return path.resolve(p, f) }) console.log(d)输出如下：[ '/usr/bin', '/usr/games', '/usr/include', '/usr/lib', '/usr/lib32', '/usr/lib64', '/usr/libexec', '/usr/libx32', '/usr/local', '/usr/sbin', '/usr/share', '/usr/src' ]如果只想获取目录下的文件而不包含文件夹，可以定义一个 filter 过滤元素：const fs = require('fs') const path = require('path') const isFile = fileName => { return fs.statSync(fileName).isFile() } const p = '/usr' const d = fs.readdirSync(p).map(f => { return path.resolve(p, f) }).filter(isFile) console.log(d)rename folder 重命名文件夹使用 fs.rename() 或 fs.renameSync() 重命名文件夹。第一个参数定义当前路径，第二个参数定义新路径：const fs = require('fs') fs.rename('./tmp', './new', err => { if (err) { console.log('rename error') return } console.log('rename success') })remove folder 删除文件夹使用 fs.rmdir() 或 fs.rmdirSync() 可以删除文件夹。但删除文件夹相比其他操作稍微复杂，用到的工具可能超出你需要的部分。所以最简单的方法就是使用第三方模块：fs-extra 来处理。它是 fs 的替代品，在 fs 模块的基础上提供了很多新功能。使用 fs-extra 的 remove() method 可以实现删除文件夹的功能：const fs = require('fs-extra') const p = './new' fs.remove(p, err => { if (err) { console.log('remove error') return } console.log('remove success') })也可以使用 promise 模式：fs.remove(p) .then(() => console.log('remove success')) .catch((err) => console.log(err))还可以使用 async/await 模式：const rm = async (p) => { if (!fs.existsSync(p)) { console.log('folder does not exist') return } try { await fs.remove(p) console.log('remove success') } catch (error) { console.log('remove error') } } rm(p)下一章我们继续了解 fs 和 path 模块的详细内容。
node.js 入门教程之五 -- HTTP request and response
搭建 http server下面是一个 http server 的示例：const http = require('http') process.env.PORT = 3000 const port = process.env.PORT const server = http.createServer((req, res) => { res.statusCode = 200 res.setHeader('Content-Type', 'text/html') res.end(' hello world ') }) server.listen(port, () => console.log(`server running at port ${port}`))访问 http://localhost:3000 就会看到页面显示 hello world。下面我们简单解释下以上示例的执行过程：首先引入 http module设置环境变量 PORT 为 3000，然后赋值给 port 变量创建一个 server，其中含有一个 callback function，用户访问 server 时会被调用设置监听端口并启动 server，当 server 启动成功后，会执行 server.listen 内定义的 callback function当 server 收到用户的请求时，会触发 request event 并提供了两个数据：一个 request(http.IncomingMessage object) 和一个 response(http.ServerResponse object)。request 提供了 request 的详细信息，可以用来获取请求 hreaders 和请求数据。response 用来操作将要发送给客户端的数据。在上面的示例中，我们修改了 response 相关信息：设置 statusCode 为 200设置一个 header结束 response 并将响应内容作为数据传入 end()发起 GET 请求下面示例发起一个 GET 请求：const https = require('https') const options = { hostname: 'niekun.net', port: 443, path: '/', method: 'GET' } const req = https.request(options, res => { console.log(`statusCode is：${res.statusCode}`); res.on('data', d => { process.stdout.write(d) }) }) req.on('error', err => console.log(err)) req.end()以上示例执行过程为：首先引入 https 模块。创建一个 options object 存储请求的信息。创建一个 https request，使用 options 作为请求信息，当请求成功后会执行后面定义的 callback function。定义一个 event handler 事件管理器来处理当 error 被触发时的响应。最后结束这个 request发起 POST 请求下面是一个 POST 请求示例：const https = require('https') const data = JSON.stringify({ todo: 'go to sleep' }) const options = { hostname: 'niekun.net', port: 443, path: '/', method: 'POST', Headers: { 'Content-Type': 'application/json', 'Content-Length': data.length } } const req = https.request(options, res => { console.log(`statusCode is：${res.statusCode}`); res.on('data', d => { process.stdout.write(d) }) }) req.on('error', err => console.log(err)) req.write(data) req.end()POST 请求相比 GET 请求多了 request.write(data) 部分，因为 POST 是把数据消息放在 body 中的。PUT 和 DELETE 请求和 POST 格式相同，只需要修改 request.method 即可。使用 Axios 库发起 POST 请求上面介绍了是应用 https 模块来发起 POST 请求，我们也可以使用第三方库使代码更加简洁，这里介绍 axios 库实现(需要通过 npm 安装)：const axios = require('axios') axios .post('http://localhost:3000', { todo: 'go to sleep' }) .then(res => { console.log(`statusCode is: ${res.status}`) process.stdout.write(res.data) }) .catch(err => console.log(err))获取 http request body 数据下面介绍 server 端如何提取在 http request body 中的 json 数据。如果你使用 Express 模块创建 http server，那么只需要使用它的 body-parser 内容拆分模块即可实现提取 json 数据，Express 模块在第一章做过示例，如果客户端使用上面的 axios 示例发起请求，服务端代码如下：const express = require('express'); const app = express(); process.env.PORT = 3000 const port = process.env.PORT app.use( express.urlencoded({ extended: true }) ) app.use(express.json()) app.get('/', (req, res) => { res.statusCode = 200 res.setHeader('Content-Type', 'text/html') res.end(' hello world ') }) app.post('/', (req, res) => { console.log(req.body.todo) res.statusCode = 200 res.setHeader('Content-Type', 'text/html') res.end('hello world\n') }); const server = app.listen(port, () => console.log('Server ready'));我们使用 express 模块创建了一个 server，并同时监听 GET 和 POST 请求并定义了 callback 响应，通过 app.use 设置了对请求 body 数据的 json 支持。当启动上面实例中使用 axios 开启的服务端的程序后，server 端收到 POST 请求后就会提取请求 body 中的 todo 对应内容并输出到终端。客户端成功收到 response 后会输出 200 状态码和响应内容。我们通过浏览器直接访问 localhost:3000 会发起一个 GET 请求，浏览器会显示 hello world。如果你不想使用 express 模块创建 server，那么 server 端要提取 POST request body 中的 json 数据就稍微麻烦一些。首先需要理解的是当我们通过 http.createServer() 创建了一个 http 服务后，callback function 会在所有的 request headers 内容都接收到后被调用，而不是 request body。connection callback 中传递的完整的 request object 数据是在 stream 流中。所以我们必须监听传输的 request body 的内容，它们是在一些 chunks 数据块中的。首先我们通过监听 data event 获取 body 数据，data event 在传输过程中会被多次触发，当 request 数据传输完毕后 end event 会被触发。通过下面的代码可以获取到完整的 body 数据：const server = http.createServer((req, res) => { let data = ''; req.on('data', chunk => { data += chunk; }) req.on('end', () => { console.log(JSON.parse(data).todo) }) })以上代码中，data 最终存储了 request body 的数据，然后通过 JSON.parse method 可以解析 json 内容。server 端完整的代码如下：const http = require('http') process.env.PORT = 3000 const port = process.env.PORT const server = http.createServer((req, res) => { let data = ''; req.on('data', chunk => { data += chunk; }) req.on('end', () => { console.log(JSON.parse(data).todo) }) res.statusCode = 200 res.setHeader('Content-Type', 'text/html') res.end(' hello world ') }) server.listen(port, () => console.log(`server running at port ${port}`))以上就是 http request 和 response 部分的简单介绍。