2017-05-05 359 views
3

我发现这个"Rendering PDFs with React components"关于在流星服务器端创建PDF文件,然后将它们发送回客户端的themeteorchef教程。我没有真的需要为PDF文件,但DOCX文件,而不是与想,也许我可以officegen为什么fs.readFileSync不会在承诺内返回任何内容?

创建的docx文件时,请按照类似的方法我创建的产生从客户方输入一个的docx文件非常类似的服务器端模块然后尝试将它们转换为base64字符串,然后应该将其发送到客户端。但是,base64字符串永远不会被创建。

这里的模块:

let myModule; 
 

 
const getBase64String = (loc) => { 
 
    try { 
 
    const file = fs.readFileSync(loc); 
 
    return new Buffer(file).toString('base64'); 
 
    } catch (exception) { 
 
    myModule.reject(exception); 
 
    } 
 
} 
 

 
const generateBase64Docx = (path, fileName) => { 
 
    try { 
 
    myModule.resolve({fileName, base64: getBase64String(path+fileName)}); 
 
    fs.unlink(loc); 
 
    } catch (exception) { 
 
    myModule.reject(exception); 
 
    } 
 

 
} 
 

 
const formatComponentAsDocx = (props, fileName) => { 
 
    try { 
 
    var docxFile = officegen({ 
 
     'type': 'docx', 
 
     'orientation': 'portrait', 
 
     'title': props.title, 
 
    }); 
 

 
    var pObj = docxFile.createP(); 
 
    pObj.addText(props.body); 
 

 
    var path = './'; 
 
    output = fs.createWriteStream(path+fileName); 
 

 
    docxFile.generate(output); 
 
    return path; 
 

 
    } catch (exception) { 
 
    myModule.reject(exception); 
 
    } 
 

 
} 
 

 
const handler = ({props, fileName}, promise) => { 
 
    myModule = promise; 
 
    const path = formatComponentAsDocx(props, fileName); 
 
    if (path) { 
 
    generateBase64Docx(path, fileName); 
 
    } 
 
} 
 

 
export const generateComponentAsDocx = (options) => { 
 
    return new Promise((resolve, reject) => { 
 
    return handler(options, { resolve, reject }); 
 
    }); 
 
};

这里的问题是fs.readFileSync一部分。它总是返回空缓冲区,这就是为什么文件永远不会转换为base64字符串,也永远不会发送回客户端。为什么?文件本身始终在服务器上创建并始终可以找到。

如果我改变const file = fs.readFileSync(loc);部分例如本

fs.readFile(loc, (err, data) => { 
if(err) myModule.reject(err); 
console.log(JSON.stringify(data)); 
} 

我可以看到数据一些数据,但还不足以对整个文件。

我在这里做错了什么?我错过了什么吗?

+0

从Google Cloud中删除文件并试图呈现被动呈现给客户端时,我遇到了此问题。文件被正确删除,但它们的句柄不会被反应性地呈现。刷新后它们才会呈现。所以这两种情况下的根本问题是一样的。 – zaplec

+0

我打算在黑暗中提出一个关于'fs.unlink(loc);'是否可以在错误的时间踢入的暗杀机会,但后来认识到'loc'没有在那个时候定义... Next :是'formatComponentAsDocx()'同步调用?我不会猜到,但如果它不_,你会不会调用'generateBase64Docx()'来尽快写出它的输出? – TripeHound

回答

2

你需要等待,直到officegen生成的文件在你尝试从其中获取base64之前完成。这是你需要做的最小改变。我不建议等待由officegen生成的finalize事件,因为此事件为buggy。我建议等待输出流的finish事件。不过,也有其他问题与你展示的代码:

  1. 既然你的代码取消链接使用后立即它的文件,然后我推断你不需要的文件。所以你可以在内存中创建数据并从中获得一个base64字符串。

  2. myModule整个rigmarole是可怕的糟糕的设计。如果我的一位同事提交了这样的代码,就会交换强烈的话语。是的,这是不好。将整个代码库转换为承诺工作好得多。

整个模块可以简化为以下内容。我对这段代码做了一点小小的测试,但我并没有声称它会处理所有可能的事情。

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") }; 
    }); 
}; 
+1

这两个答案,这个和Styx的答案都很好,引导我走向正确的方向,并实际解决了问题。不过,我会将此标记为正确的答案,因为这更为详尽,并告诉我为什么我宁愿使用“完成”而不是“最终确定”。谢谢! – zaplec

-2

readFileSync是同步的,所以它不处理承诺。

https://nodejs.org/api/fs.html#fs_fs_readfilesync_file_options

Synchronous version of fs.readFile. Returns the contents of the file. 

你可能想使用fs.readFile。

https://nodejs.org/api/fs.html#fs_fs_readfile_file_options_callback

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. 
+0

我不认为这是问题所在。我创建了一个与创建PDF文件的教程类似的模块,然后fs.readFileSync将文件返回到缓冲区。 – zaplec

+0

也许,看不到代码被调用的地方,但是你在你的问题中提到了承诺,并且因为它是同步的,所以我坚持不懈。你正在试图编写文件并在写完后检索它吗?如果是这种情况,写入方法应该是异步的。现在没有想法。祝你好运 – chairmanmow

+0

* generateComponentAsDocx *在客户端使用基本的Meteor.call(...)从流星服务器方法调用。只有道具被传递给模块,因为它是这个解决方案中唯一需要的外部数据。 – zaplec

2

您的问题是docxFile.generate(output);不同步。因此,当你的本地路径存在时(它是由fs.createWriteStream()调用创建的),它是空的,并且你的同步fs.readFileSync正在捕获那个空文件。

您应该订阅docxFilefinalize事件捕捉文件的生成结束:

docxFile.on('finalize, function (writtenBytes) { 
    // do you work with generated file here 
}); 

因此,重写代码:

const handler = ({props, fileName}, promise) => { 
    myModule = promise; 
    formatComponentAsDocx(props, fileName); 
} 

const formatComponentAsDocx = (props, fileName) => { 
    try { 
    var docxFile = officegen({ 
     'type': 'docx', 
     'orientation': 'portrait', 
     'title': props.title, 
    }); 

    var pObj = docxFile.createP(); 
    pObj.addText(props.body); 

    var path = './'; 
    output = fs.createWriteStream(path+fileName); 

    docxFile.on('error', function (err) { 
     myModule.reject(err); 
    }); 

    docxFile.on('finalize', function() { 
     generateBase64Docx(path, fileName); 
    }); 

    docxFile.generate(output); 

    } catch (exception) { 
    myModule.reject(exception); 
    } 
} 
相关问题