node.js

103 阅读5分钟
并发和吞吐量

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: 获取文件扩展名
  1. 从路径中获取信息
// 通过为 basename 指定第二个参数来获取不带扩展名的文件名
const notes = '/users/joe/notes.txt';
path.basename(notes, path.extname(notes))

  1. 使用路径

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: trueforce: true } 使得文件夹如果不存在,异常将被忽略

const fs = require('fs');
fs.rm(dir, { recursive: trueforce: 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