NodeJS中有很多异步API,比如常见的fs模块的readFile方法。虽然有同步的版本readFileSync, 但是其性能肯定不如前者。所以这里从异步异步版本readFile说起:
const fs = require('fs');
fs.readFile('./a.txt', 'utf-8', function(error, data) {
if (!error) {
console.log('a.txt data:', data);
}
});
函数本身比较简单,三个参数分别是文件路径,数据编码和回调函数。
现在有这样一个需求,分别读取abc三个txt文件(文件内容分别是1, 2, 3,文件路径和js文件路径相同),按序输出文件内的内容,也就是输出1 2 3,如果并列读取,像这样:
const fs = require('fs');
fs.readFile('./a.txt', 'utf-8', function(error, data) {
if (!error) {
console.log('a.txt data:', data);
}
});
fs.readFile('./b.txt', 'utf-8', function(error, data) {
if (!error) {
console.log('b.txt data:', data);
}
});
fs.readFile('./c.txt', 'utf-8', function(error, data) {
if (!error) {
console.log('c.txt data:', data);
}
});
多次运行,会发现每次输出的顺序不一致:
当然不止这三种,有兴趣可以多运行几次看看。
所以要出现1 2 3固定输出,得这样写,读完a.txt才能读b,读完b才能读c:
const fs = require("fs");
fs.readFile("./a.txt", "utf-8", function (error, data) {
if (!error) {
console.log("a.txt data:", data);
fs.readFile("./b.txt", "utf-8", function (error, data) {
if (!error) {
console.log("b.txt data:", data);
fs.readFile("./c.txt", "utf-8", function (error, data) {
if (!error) {
console.log("c.txt data:", data);
}
});
}
});
}
});
这样虽然是按序输出了,但是代码嵌套了,如果有更多文件,且读取后的处理逻辑更加复杂,整个代码的可读性就变得很差。这个就是回调地狱。为了解决这个方法,我们引入了Promise。
Promise可以简单理解为包裹异步函数的容器,基本示例:
const fs = require("fs");
const readAPromise = new Promise(function (resolve, reject) {
fs.readFile("./a.txt", "utf-8", function (error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
readAPromise.then(function(result) {
console.log(result);
}).catch(function(error) {
console.error(error);
});
先试试链式调用来改造回调地狱代码:
const fs = require("fs");
function readFile(filePath, defaultCoding = "utf-8") {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, defaultCoding, function (error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
readFile("./a.txt")
.then(function (data) {
console.log(data);
return readFile("./b.txt");
})
.then(function (data) {
console.log(data);
return readFile("./c.txt");
})
.then(function (data) {
console.log(data);
})
.catch(function (error) {
console.error(error);
});
然后用Promise.all试试:
const fs = require("fs");
function readFile(filePath, defaultCoding = "utf-8") {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, defaultCoding, function (error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
Promise.all([readFile("./a.txt"), readFile("./b.txt"), readFile("./c.txt")])
.then(function (data) {
console.log(data);
})
.catch(function (error) {
console.error(error);
});
这里封装了一个返回文件读取结果Promise函数。然后调用Promise.all,第一个参数是个Promise对象数组。如果全部成功,就会到.then中的data去,data是各个promise resolve的结果数组,这里打印[ '1', '2', '3' ];如果有一个失败,整个Promise数组将走到.catch。要解决这个问题,可以尝试Promise.allSettled
const fs = require("fs");
function readFile(filePath, defaultCoding = "utf-8") {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, defaultCoding, function (error, data) {
if (filePath === './b.txt') {
reject('cannot read b file');
}
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
Promise.allSettled([readFile("./a.txt"), readFile("./b.txt"), readFile("./c.txt")])
.then(function (data) {
console.log(data);
})
.catch(function (error) {
console.error(error);
});
result:
[
{ status: 'fulfilled', value: '1' },
{ status: 'rejected', reason: 'cannot read b file' },
{ status: 'fulfilled', value: '3' }
]