Skip to content

Node.js 读写文件

Node.js 是一个异步事件驱动的 JavaScript 运行环境,读写文件有同步和异步区别。在使用fs模块之前,需要有回调函数、promise对象的基础知识。

回调函数

在 JavaScript 中,回调函数具体的定义为:函数 A 作为参数(函数引用)传递到另一个函数 B 中,并且函数 B 运行完成后再执行函数 A。我们就把函数 A 叫做回调函数。例如:

function doSomething(msg, callback) {
alert(msg);
if (typeof callback == "function")
callback();
};
doSomething("存 5000 块", function () {
alert("稍等,马上办理");
alert('2 分钟后,您的业务已办理完毕');
});

回调与同步、异步并没有直接的联系,回调只是一种实现方式,既可以有同步回调,也可以有异步回调。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用,用于对该事件或条件进行响应。

同步与异步的区别

下面这段代码的执行结果,就能看出不同区别:

function doSomething(msg, callback) {
console.log(msg);
if (typeof callback == "function")
callback();
};
doSomething("存 5000 块", function () {
console.log("稍等,马上办理");
setTimeout(function () { console.log('2 分钟后,您的业务已办理完毕'); }, 2000);
// console.log('2 分钟后,您的业务已办理完毕');
});
setTimeout(function () { console.log('5'); }, 5000);
console.log('1');
function test() {
setTimeout(function () { console.log('2'); }, 1000);
}
test();
console.log('3');
setTimeout(function () { console.log('4'); }, 2000);

与过程式编程相比,异步对变成带来的一些变化:因为有很多情况下是代码要求先前的代码执行完毕,如要调用之前处理的数据结果、和数据库交互等。Node.js 中可以采用回调方式解决这个问题。

使用fs模块读取文件

有同步、异步和promise对象三种方式,推荐使用promise对象。

先看一个案例:

var fs = require('fs');
fs.readFile('./files/1.txt', 'utf8', function (err, data) {
if (err) {
throw err;
}
console.log(data);
});
fs.readFile('./files/2.txt', 'utf8', function (err, data) {
if (err) {
throw err;
}
console.log(data);
});
fs.readFile('./files/3.txt', 'utf8', function (err, data) {
if (err) {
throw err;
}
console.log(data);
});

上述代码并不能保证按照1、2、3的顺序输出结果,会按照实际读取的结果展示。因为他们都是异步操作,不知道谁先读取完。 这得取决于异步任务IO的耗时。

为了确保按照顺序执行,一开始,人们的解决方案是在回调函数中,使用回调函数。代码如下:

fs.readFile('./files/1.txt', 'utf8', function (err, data) {
if (err) {
throw err;
} else {
console.log(data);
fs.readFile('./files/2.txt', 'utf8', function (err, data) {
if (err) {
throw err;
} else {
console.log(data);
fs.readFile('./files/3.txt', 'utf8', function (err, data) {
if (err) {
throw err;
}
console.log(data);
});
}
});
}
});

以上按照顺序执行多个异步任务产生的问题:回调地狱问题(层层包裹进行回调,代码也不够优雅)

在ES6中,提供了promise对象来解决上述问题。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

promise对象

Promise 对象是由关键字 new 及其构造函数来创建的。该构造函数会把一个叫做“处理器函数”(executor function)的函数作为它的参数。这个“处理器函数”接受两个函数——resolvereject ——作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。

new Promise返回的是一个promise对象,这个对象有一个方法叫做then,在其原型对象上通过这then方法可以指定成功和失败的回调函数。

语法:promise.then(successCallback,errorCallback);

promise.then(function (data) {
//then第一个函数是成功的回调,参数是resolve(err)中的data
console.log('成功:' + data); // 若成功,运行结果:成功:111
}).catch(function(err){
//then第二参数错误回调换成这里catch也行,两者选其一
console.log('err');
}).finally(function(){
//无论失败成功都会执行
console.log('完成');
})

例如,可以自定义promise方式读取文件:

function readFilePromise(path, encoding = "utf8") {
const promise = new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) return reject(err);
return resolve(data.toString(encoding));
});
});
return promise;
}
readFilePromise("./package.json").then(res => console.log(res));

在 Node.js v12 中,引入了 fs Promise api。它们返回 Promise 对象而不是使用回调。 API 可通过 require('fs').promises 访问。

const fsPromises = require("fs").promises;
fsPromises.readFile("./package.json", {encoding: "utf8", flag: "r"})
.then(console.log)
.catch(console.error);