2017-05-05 361 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); 
    } 
} 
相關問題