本质上来说 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);
如果我们在编程中没有使用指定的数据类型,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 的开源项目:
通过这 14 篇教程,我们介绍了 node.js 的基本语法和相关使用场景。后期可以在具体项目中进行更加详细的体验。
]]>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 有如下优势:
可以通过状态判读符来判断当前运行环境,执行不同指令:
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。注意使用类型判断符===
是为了保证类型和数值都匹配。
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 块。
当我们在浏览器中使用 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 首先引入的概念,unix 操作系统在很久之前就在使用了,一个程序可以通过 pipe 管道操作符 |
来传递 streams 流给其他程序。
下面的示例是在 Linux 中,通过 pipe 管道将 cat 读取的文件数据传递给 grep 进行过滤,test.txt 文件内容如下:
aaa bbb
bbb ccc
aaa ccc
$ cat test.txt | grep aaa
aaa bbb
aaa ccc
在传统方法中,当程序读取一个文件内容时会先将文件内容全部读取到内存中,然后再去使用它。而使用 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 数据传输给客户端。这样就会一边读取文件一边传输数据。
以上示例中调用 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 核心模块提供了 stream 的原生支持,以下是常用的部分:
有四个 streams 的 calsses:
从 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 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,并建立 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())
})
使用 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 的数据状态。
和可写的 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
]]>node.js 中 buffer 通过 Buffer class 实现。
引入 Buffer 是为了帮助开发者处理 binary 二进制数据。传统的 ecosystem 只可以处理 strings 字符串数据。Buffer 和 streams 是紧密关联的,当 stream processor 流处理器接收数据的速度大于其处理速度时,就会先将数据放在 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 数据:
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 核心无需单独安装,使用下面命令引入模块:
const http = require('http')
模块提供了很多 properties,methods 和 classes。
http.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_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.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
}
http.createServer()
返回一个 http.Server class 实例。
const server = http.createServer((req, res) => {
//handle every single request with this callback
})
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.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))
http 模块提供 5 个 calsses:
node.js 会创建一个全局的 http.Agent instance 实例来管理 server 同 http 客户端链接的持续连接和链接复用,这个 object 可以确保一个客户端对服务端发起的一系列请求是按队列排序的,且使用单独的 socket 接口。同样 agent object 管理着一个 sockets 池,这是影响性能的关键因素。
当 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.createServer()
创建一个 server 时会返回 http.Server 实例。 http.Server 实例需要使用它的以下 method:
close()
停止 server 接收新的链接listen()
启动 server 并开始监听请求http.createServer()
的 callback 监听 request event 并响应。
由 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:
服务端编辑好 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.IncomingMessage 可以在以下两个地方创建:
它可以用来访问 response 数据:
在 http.IncomingMessage 生效一个可读的 stream 接口后,数据可在 stream 中读取。
]]>events 模块提供了 EventEmitter class 它是处理 events 的关键工具。
引入及初始化 events 模块如下:
const EventEmitter = require('events')
const eventEmitter = new EventEmitter()
每个 event listener 事件监听器都是互相独立的,同时它们使用如下 events:
下面介绍常用的一些 method。
eventEmitter.addListener()
和 eventEmitter.on()
功能相同。可以给某个 event 添加一个 listener,语法如下:
eventEmitter.addListener('test', () => {})
eventEmitter.emit()
用来触发一个信号,信号触发后,对应信号的 event listener 会响应 callback function:
eventEmitter.addListener('test', () => { console.log('test emited') })
eventEmitter.emit('test')
//output:
//test emited
eventEmitter.eventNames()
会返回当前 EventEmitter object 内注册已的 events 为一个字符串数组:
eventEmitter.addListener('test1', () => {})
eventEmitter.addListener('test2', () => {})
console.log(eventEmitter.eventNames())
//output:
//[ 'test1', 'test2' ]
eventEmitter.getMaxListeners()
返回当前 EventEmitter object 能添加的 event listener 最大数量。默认为 10 个,可以通过 setMaxListeners()
修改。
eventEmitter.listenerCount()
返回某个 event 定义的 listeners 数量:
eventEmitter.addListener('test4', () => { console.log('a') })
eventEmitter.addListener('test4', () => { console.log('b') })
console.log(eventEmitter.listenerCount('test4'))
//output:
//2
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.removeListener()
功能相同。可以删除一个 event 的某个 listener,需要指定 event 名称和 listener callback 名称:
const f3 = () => {}
eventEmitter.addListener('test6', f3)
eventEmitter.off('test6', f3)
eventEmitter.on()
同 eventEmitter.addListener()
功能相同。可以给某个 event 添加一个 listener:
eventEmitter.on('test6', () => {})
eventEmitter.once()
监听一个 event 且其 callback function 只能被执行一次:
eventEmitter.once('test7', () => console.log('once show once'))
eventEmitter.emit('test7')
eventEmitter.emit('test7')
//output:
//once show once
当通过 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 listener
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 listener
eventEmitter.removeAllListeners()
可以删除指定 event 的所有 listeners:
eventEmitter.removeAllListeners('test9')
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 object 最大可以添加的 listeners 数量:
eventEmitter.setMaxListeners(20)
console.log(eventEmitter.getMaxListeners())
//output:
//20
以上就是 events 模块最常用功能的介绍。
]]>它集成在 node.js 中,只需要引入即可:
const os = require('os')
这里先介绍一些在处理文件时有用的 properties:
\n
on Linux and macOS, and \r\n
on Windows完整的 signals 信号列表参考:https://nodejs.org/api/os.html#os_signal_constants
下面介绍 os 模块常用的 method。
os.arch()
会返回底层架构的字符串,如:arm, x64, arm64:
console.log(os.arch())
//output:
x64
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
}
}
]
根据 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/Endianness
os.freemem()
返回系统可用内存,以字节为单位:
console.log(os.freemem())
//output:
574537728
os.homedir()
返回系统当前用户的 home 目录路径:
console.log(os.homedir())
//output:
/home/marco
os.hostname()
返回 hostname 信息:
console.log(os.hostname())
//output:
marco-virtual-machine
os.loadavg()
返回系统计算的平均加载时间,此 method 只在 Linux/macOS 下有意义:
console.log(os.loadavg())
//output:
[ 0.08, 0.17, 0.13 ]
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()
返回当前安装的 node.js 的编译平台,可能的返回值为:
os.release()
返回当前系统的版本号信息:
console.log(os.release())
//output:
5.8.0-41-generic
os.tmpdir()
返回系统的临时文件夹路径:
console.log(os.tmpdir())
//output:
/tmp
os.totalmem()
返回系统的总内存数量,单位为 byte 字节:
console.log(os.totalmem()/1024/1024)
//output:
1958.45703125
os.type()
返回当前操作系统类型,返回值可以有:
os.uptime()
返回系统从上次开机来已运行的总时间,单位为秒:
console.log(os.uptime()/3600)
//output:
24.260555555555555
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 模块提供了很多有用的功能来对 file system 进行访问和交互。并且他是集成在 node.js core 中的,并不需要手动安装:
const fs = require('fs')
通过以上方式引入 fs 模块后,我们就可以使用它的各种功能了。包括:
fs 模块的一个特性是它的以上所有 method 默认都是 asynchronous 异步的,给 method 后加上 Sync
就可以使用同步模式,例如:
同步模式下通过使用 try/catch 块来处理 error 情况。同步模式下程序会在处理文件过程中出现进程阻塞直到处理完成,在编程中可以根据需要灵活运用。
path 模块也提供了很多实用的功能来访问文件系统以及交互。同样的它也是集成在 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 介绍。
返回路径上的最后一部分,通过第二个参数可以过滤掉扩展名:
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
file
返回路径上的文件夹部分:
console.log(path.dirname('/home/marco/file'))
//output:
/home/marco
返回文件的扩展名:
console.log(path.extname('/home/marco/file.txt'))
//output:
.txt
返回路径是否是绝对路径:
console.log(path.isAbsolute('/home/marco'))
console.log(path.isAbsolute('./marco'))
//output:
true
false
将多个部分合并为一个路径:
const name = 'marco';
console.log(path.join('/home/' + name + '/file'))
//output:
/home/marco/file
将一个包含相对标记符的路径转换为常规绝对路径:
console.log(path.normalize('/home/marco/../test'))
//output:
/home/test
将路径拆分为一个 object,包含下面的 properties:
console.log(path.parse('/home/marco/test/file.txt'))
//output:
{
root: '/',
dir: '/home/marco/test',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
需要传入两个路径作为参数,第一个路径作为 base 基础路径,返回第二个路径相对于第一个路径的相对路径:
console.log(path.relative('/home/marco', '/home/marco/test/file.txt'))
//output:
test/file.txt
计算一个相对路径的绝对路径,基于当前程序执行的路径:
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 就是通过 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'
标记,它的意思就是以只读模式打开文件。我们可以通过使用不同的标记来以不同的方式打开文件:
更多 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 后才执行后面的指令。
每个文件都包含独自的属性信息,可以通过 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.size
每个文件在系统中都有一个路径。在 Linux 中路径格式如:/users/joe/file.txt
,在 Windows 中路径格式如:C:\users\joe\file.txt
。在程序中引用路径时需要特别注意路径的格式。
node.js 中可以使用 path 模块来处理文件路径相关数据。
如果给定一个路径,可以提取其相关数据,如:
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.txt
path.resolve 和 path.normalize 都不会检查路径是否真实存在,它们仅仅是根据提供的数据计算路径结果。
最简单的方式读取文件内容就是通过 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 模块。
最简单的写入文件的方法就是使用 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 一样,可以参考设置。
给已有文件附加内容更加方便的方法是使用 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 是一个更好的方法。
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 创建文件夹,看起来更加直观。
使用 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)
使用 fs.rename()
或 fs.renameSync()
重命名文件夹。第一个参数定义当前路径,第二个参数定义新路径:
const fs = require('fs')
fs.rename('./tmp', './new', err => {
if (err) {
console.log('rename error')
return
}
console.log('rename success')
})
使用 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 模块的详细内容。
]]>下面是一个 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('<h1>hello world</h1>')
})
server.listen(port, () => console.log(`server running at port ${port}`))
访问 http://localhost:3000 就会看到页面显示 hello world。
下面我们简单解释下以上示例的执行过程:
当 server 收到用户的请求时,会触发 request event 并提供了两个数据:一个 request(http.IncomingMessage object) 和一个 response(http.ServerResponse object)。
在上面的示例中,我们修改了 response 相关信息:
end()
下面示例发起一个 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()
以上示例执行过程为:
下面是一个 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 即可。
上面介绍了是应用 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))
下面介绍 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('<h1>hello world</h1>')
})
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('<h1>hello world</h1>')
})
server.listen(port, () => console.log(`server running at port ${port}`))
以上就是 http request 和 response 部分的简单介绍。
]]>