Javascript 为什么fs.readFileSync在serveside上的承诺内不返回任何内容?
我在MeteorChef上找到了关于在Meteor服务器端创建PDF文件并将其发送回客户端的教程。我其实不需要PDF文件,但需要docx文件,我想在使用officegen创建docx文件时也可以采用类似的方法 我创建了非常类似的服务器端模块,该模块从客户端的输入生成一个docx文件,然后尝试将它们转换为base64字符串,然后发送到客户端。但是,永远不会创建base64字符串 以下是模块:Javascript 为什么fs.readFileSync在serveside上的承诺内不返回任何内容?,javascript,node.js,meteor,promise,Javascript,Node.js,Meteor,Promise,我在MeteorChef上找到了关于在Meteor服务器端创建PDF文件并将其发送回客户端的教程。我其实不需要PDF文件,但需要docx文件,我想在使用officegen创建docx文件时也可以采用类似的方法 我创建了非常类似的服务器端模块,该模块从客户端的输入生成一个docx文件,然后尝试将它们转换为base64字符串,然后发送到客户端。但是,永远不会创建base64字符串 以下是模块: let myModule; 常量getBase64String=(loc)=>{ 试一试{ const
let myModule;
常量getBase64String=(loc)=>{
试一试{
const file=fs.readFileSync(loc);
返回新的缓冲区(file).toString('base64');
}捕获(例外){
myModule.reject(异常);
}
}
const generateBase64Docx=(路径、文件名)=>{
试一试{
解析({fileName,base64:getBase64String(path+fileName)});
财政司司长取消联系(loc);
}捕获(例外){
myModule.reject(异常);
}
}
常量formatComponentAsDocx=(道具,文件名)=>{
试一试{
var docxFile=officegen({
“类型”:“docx”,
“方向”:“肖像”,
“标题”:props.title,
});
var pObj=docxFile.createP();
pObj.addText(props.body);
变量路径='。/';
输出=fs.createWriteStream(路径+文件名);
docxFile.generate(输出);
返回路径;
}捕获(例外){
myModule.reject(异常);
}
}
常量处理程序=({props,fileName},promise)=>{
myModule=承诺;
常量路径=formatComponentAsDocx(道具,文件名);
如果(路径){
generateBase64Docx(路径、文件名);
}
}
导出常量generateComponentAsDocx=(选项)=>{
返回新承诺((解决、拒绝)=>{
返回处理程序(选项{resolve,reject});
});
};代码>readFileSync是同步的,因此它不涉及承诺
您可能想使用fs.readFile
您的问题是docxFile.generate(输出)代码>不同步。因此,当您的本地路径存在时(它是由fs.createWriteStream()
调用创建的),它是空的,并且您的同步fs.readFileSync
正在捕获该空文件
您应该订阅docxFile
的finalize
事件以捕获文件生成结束:
docxFile.on('finalize,函数(writenbytes){
//您在这里处理生成的文件吗
});
因此,重写代码:
consthandler=({props,fileName},promise)=>{
myModule=承诺;
formatComponentAsDocx(道具、文件名);
}
常量formatComponentAsDocx=(道具,文件名)=>{
试一试{
var docxFile=officegen({
“类型”:“docx”,
“方向”:“肖像”,
“标题”:props.title,
});
var pObj=docxFile.createP();
pObj.addText(props.body);
变量路径='。/';
输出=fs.createWriteStream(路径+文件名);
docxFile.on('error',函数(err){
myModule.reject(错误);
});
docxFile.on('finalize',函数(){
generateBase64Docx(路径、文件名);
});
docxFile.generate(输出);
}捕获(例外){
myModule.reject(异常);
}
}
您需要等待由officegen
生成的文件完成后,才能尝试从中取出base64。这是你需要做的最小的改变。我不建议等待由officegen
生成的finalize
事件,因为此事件是。我建议等待输出流的finish
事件。但是,您显示的代码还存在其他问题:
由于您有代码在使用文件后立即取消链接,因此我推断您不需要文件。因此,您可以在内存中创建数据,并从中获取base64
字符串
使用myModule
的整个繁琐设计是糟糕透顶的。如果我的一位同事提出了这样的准则,就会有激烈的言辞交流。是的,它是坏的。最好将整个代码库转换为使用承诺
整个模块可简化为以下内容。我已经对这段代码做了一些测试,但我并不认为它能处理所有可能发生的事情
import * as stream from "stream";
import officegen from "officegen";
function formatComponentAsDocx(props) {
return new Promise((resolve, reject) => {
// There's no need to wrap this in try...catch only to call reject. If any
// exception is raised in this function, the promise is automatically
// rejected.
const docxFile = officegen({
'type': 'docx',
'orientation': 'portrait',
'title': props.title,
});
const pObj = docxFile.createP();
pObj.addText(props.body);
// We record the output in our own buffer instead of writing to disk,
// and reading later.
let buf = Buffer.alloc(0);
const output = new stream.Writable({
write(chunk, encoding, callback) {
buf = Buffer.concat([buf, chunk]);
callback();
},
});
docxFile.generate(output, {
// Do propagate errors from officegen.
error: reject,
});
// We don't use the "finalize" event that docxFile.generate would emit
// because it is buggy. Instead, we wait for the output stream to emit
// the "finish" event.
output.on('finish', () => {
resolve(buf);
});
});
}
export function generateComponentAsDocx({ props }) {
return formatComponentAsDocx(props).then((data) => {
return { base64: data.toString("base64") };
});
};
我认为这不是问题所在。我创建了一个类似于教程中创建PDF文件的模块,在那里fs.readFileSync将文件返回到一个缓冲区中。也许,我看不出该代码是从哪里调用的,但您在问题中提到了承诺,而且因为它是同步的,所以我很想知道。您是否正在尝试写入文件并在完成写入后检索它?如果是这种情况,写入方法应该是异步的。现在没有主意了。幸运的是,generateComponentAsDocx是在客户端通过meteor服务器方法使用基本meteor.call(…)调用的。只有道具被传递给模块,因为它是这个解决方案中唯一需要的外部数据。我在从Google云删除文件并尝试将其反应性地呈现给客户端时遇到了这个问题。文件被正确删除,但它们的句柄不会被反应性地渲染掉。它们只有在刷新后才会消失。所以这两种情况的根本问题是一样的,我想建议大家在黑暗中尝试一下fs.unlink(loc)代码>可能在错误的时间启动,但随后意识到,loc
在该时间点没有定义。。。下一步:formatComponentAsDocx()
是同步调用吗?我猜不会,但如果不是,您是否会调用generateBase64Docx()
来过早地编写其输出?这两个答案,以及来自Styx的答案,都很好,引导我找到了正确的方向,并实际解决了问题。但是,我会将此标记为正确答案,因为这更详细,并且还告诉我为什么我更喜欢使用“finish”而不是“finalize”。非常感谢。
The callback is passed two arguments (err, data), where data is the contents of the file.
If no encoding is specified, then the raw buffer is returned.
import * as stream from "stream";
import officegen from "officegen";
function formatComponentAsDocx(props) {
return new Promise((resolve, reject) => {
// There's no need to wrap this in try...catch only to call reject. If any
// exception is raised in this function, the promise is automatically
// rejected.
const docxFile = officegen({
'type': 'docx',
'orientation': 'portrait',
'title': props.title,
});
const pObj = docxFile.createP();
pObj.addText(props.body);
// We record the output in our own buffer instead of writing to disk,
// and reading later.
let buf = Buffer.alloc(0);
const output = new stream.Writable({
write(chunk, encoding, callback) {
buf = Buffer.concat([buf, chunk]);
callback();
},
});
docxFile.generate(output, {
// Do propagate errors from officegen.
error: reject,
});
// We don't use the "finalize" event that docxFile.generate would emit
// because it is buggy. Instead, we wait for the output stream to emit
// the "finish" event.
output.on('finish', () => {
resolve(buf);
});
});
}
export function generateComponentAsDocx({ props }) {
return formatComponentAsDocx(props).then((data) => {
return { base64: data.toString("base64") };
});
};