并发和吞吐量
Node.js中的 Javascript 执行是单线程的,因此并发是指时间循环在完成其他工作后执行 Javascript 回调函数的能力,任何预期以并发方式运行的代码都必须允许事件循环在非 Javascript 操作 (如 I/O) 发生时继续运行
例如,让我们考虑这样一种情况:每个对 Web 服务器的请求都需要 50 毫秒才能完成,而这 50 毫秒中有 45 毫秒是可以异步完成的数据库 I/O。 选择 非阻塞 异步操作可以释放每个请求 45 毫秒的时间来处理其他请求。 这仅仅是选择使用 非阻塞 方法而不是 阻塞 方法在容量上的显着差异。
事件循环不同于许多其他语言中的模型,在这些模型中可以创建额外的线程来处理并发工作
process.NextTick()
Node.js 的 process.NextTick 函数以一种特殊的方式与事件循环交互
setImmiediate()
在当前操作结束后,传递给 process.nextTick() 的函数将在事件循环的当前迭代中执行。 这意味着它将始终在 setTimeout 和 setImmediate 之前执行。
延迟为 0 毫秒的 setTimeout() 回调与 setImmediate() 非常相似。 执行顺序将取决于各种因素,但它们都将在事件循环的下一次迭代中运行。
prpcess.nextTick 回调添加到process.nextTick queue, Promise.then回调添加到 microTask queue, macrotask queue 添加了 setTimeout, setImmediate 回调
事件循环先执行process.nextTick queue,然后执行 Promises microTask queue, 再执行 macrotask queue
Node.js事件触发器
const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
eventEmitter.on('start', number => { console.log(`started ${number}`); });
eventEmitter.emit('start', 23);
文件统计信息
const fs = require('fs);
async function example() {
fs.stat('/Users/joe/test.txt', (err, stats) => {
if (err) {
console.error(err);
return;
}
stats.isFile(); // true
stats.isDirectory(); // false
stats.isSymbolicLink(); // false
stats.size; // 1024000 //= 1MB
})
}
文件路径
给定一条路径,可以获取如下信息:
- dirname: 获取文件的父文件夹
- basename: 获取文件名部分
- extname: 获取文件扩展名
- 从路径中获取信息
// 通过为 basename 指定第二个参数来获取不带扩展名的文件名
const notes = '/users/joe/notes.txt';
path.basename(notes, path.extname(notes))
- 使用路径
const path = require('path')
const name = 'joe';
// 使用 path.join() 连接路径的两个或多个部分
path.join('/', 'users', 'name', 'notes.txt') // '/users/name/notes.txt'
// 使用 path.resolve()获取相对路径的绝对路径
// 不传参数的时候是 附加到当前目录下
path.resolve('joe.txt') // '/Users/joe/joe.txt'
// 传参数的时候,附加到第一个参数的文件夹下
path.resolve('temp', 'joe.txt') // '/Users/joe/temp/joe.txt'
// 传参数的时候,第一个参数如果带斜杠,则代表是绝对路径
path.resolve('/etc', 'joe.txt') // '/etc/joe.txt'
// path.normalize() 尝试计算包含.. . // 这种相对说明符的实际路径
path.normalize('/users/joe/..//test.txt') // '/users/test.txt'
resolve()和normalize()都不会检查路径是否存在,仅仅是计算出一条路径
文件描述符
文件描述符是对打开文件的引用,是使用fs模块提供的open()方法打开文件返回的数字fd,此编号可以唯一标识操作系统中打开的文件
const fs = require('fs')
fs.open('/Users/joe/test.txt', 'r', (err, fd) => {
// fd 就是文件的描述符
})
| 标志 | 描述 | 如果文件不存在则创建文件 |
|---|---|---|
| r+ | 此标志打开文件的读取和写入 | no |
| w+ | 此标志打开文件的读取和写入, 并将流定位在文件的开始 | no |
| a | 此标志打开文件的写入,并将流定位在文件的结尾 | yes |
| a+ | 此标志打开文件的读取和写入, 并将流定位在文件的结尾 | yes |
使用Node.js读取文件
在Node.js中读取文件最简单的方法是fs.readFile(),将文件路径,编码和将使用文件数据(以及错误)调用的回调函数传递给它
const fs = require('fs');
fs.readFile('/Users/joe/test.txt', 'utf-8', (err, data)=> {
console.log(data);
})
同步版本: fs.readFileSync()
const fs = require('fs');
try {
const data = fs.readFileSync('/Users/joe/test.txt', 'utf-8');
console.log(data);
} catch (err) {
console.err(err)
}
基于Promise的fsPromises.readFile()方法
const fs = require('fs/promises')
async function example() {
try {
const data = await fs.readFile(/Users/joe/test.txt', { encoding: 'utf-8' })
console.log(data)
} catch(err) {
console.log(err)
}
}
example();
fs.readFile(), fs.readFileSync(), fsPromises.readFile() 这三个都在返回数据前读取了内存中文件的全部内容。 这意味着大文件将对你的内存消耗和程序执行速度产生重大影响。 这种情况下最好使用流读取文件内容。
使用Node.js写入文件
在Node.js中写入文件最简单的方法是使用fs.write()API
const fs = require('fs');
const content = 'Hello world!'
fs.writeFile('/Users/joe/test.txt', content, err => {
if (err) { console.error(err); }
})
// 同步写入文件
const fs = require('fs');
try {
const content = 'Hello world!'
fs.writeFileSync('/Users/joe/test.txt', content)
} catch(err) {
console.error(err);
}
// 使用 fs/Promises 同步写入文件
const fsPromises = require('fs/promises');
try {
const content = 'Hello world!'
await fsPromises.writeFile('/Users/joe/test.txt', content)
} catch(err) {
console.error(err);
}
将内容附加到文件
const fs = require('fs');
const content = 'Hello world!'
fs.appendFile('/Users/joe/test.txt', content, err=> {
console.log(err)
});
// 使用 fs/Promises 追加文件内容
const fsPromises = require('fsPromises');
try {
const content = 'Hello world!'
await fsPromises.appendFile('/Users/joe/test.txt', content);
} catch(err) {
console.log(err)
}
在Node.js中使用文件夹
检查文件夹是否存在
使用 fs.access()或者 fsPromises.access() 检查文件夹是否存在以及 Node.js 是否可以使用其权限访问它
新建一个文件夹
// 使用 fs.mkdir(), fs.mkdirSync(), fsPromises,mkdir() 创建新文件夹
const fs = require('fs');
const folderName = '/Users/joe/test';
try {
if (!fs.existsSync(folderName)) {
fs.mkdir(folderName)
}
} catch(err) {
console.err(err)
}
读取目录的内容
// 使用 fs.readdir(), fs.readdirSync(), fsPromises.readdir() 读取目录的内容(包括文件和子文件夹,并返回相对路径)
const fs = require('fs');
const folderPath = '/Users/joe';
fs.readdirSync(folderPath);
// 获取完整路径
fs.readdirSync(folderPath).map(fileName => {
return path.join(folderPath, fileName);
})
// 仅返回文件,排除文件夹
const isFile = fileName => {
return fs.lstatSync(fileName).isFile()
}
fs.readdirSync(folderPath).map(fileName => {
return path.join(folderPath, fileName);
}).filter(isFile)
重命名文件夹
// 使用 fs.rename(), fs.renameSync(), fsPromises.rename()给文件夹重命名
const fs = require('fs');
fs.rename('/Users/joe', '/Users/roger');
删除文件夹
// 使用 fs.rmdir(), fs.rmdirSync(), fsPromises.rmdir()删除文件夹
const fs = require('fs');
fs.rmdir(dir, err => {
if (err) {
console.log(err)
}
})
要删除包含内容的文件夹,使用 fs.rm(), 和 选项 { recursive: true } 递归删除内容
{ recursive: true, force: true } 使得文件夹如果不存在,异常将被忽略
const fs = require('fs');
fs.rm(dir, { recursive: true, force: true }, err => {
if (err) {
console.log(err)
}
})
从命令行运行Node.js
运行 Node.js 程序的常用方法是运行全局可用的 node 命令,并传递要执行的文件的名称
node app.js
如何从Node.js 读取环境变量
Node.js 的 process 模块提供了 env 属性,它承载了进程启动时设置的所有环境变量
USER_ID=23498 USER_KEY=foorbar node app.js
// 在代码中访问环境变量
process.env.USER_ID // 23498
process.env.USER_KEY // foorbar
如果你的 node 项目中有多个环境变量,你也可以在你的项目根目录下创建一个 .env 文件,然后在运行时使用dotenv包加载他们
# .env file
USER_ID="23498"
USER_KEY="foobar"
NODE_ENV="development"
在js文件中:
require('dotenv').config()
process.env.USER_ID // 23498
process.env.USER_KEY // foobar
process.env.NODE_ENV // development