2017-06-29 50 views
0

承諾鏈對於不覆蓋節點中的流(請參閱Bergi的評論)非常有用。但這裏有一個例子,在非同步微妙的情況下產生的寫流錯誤命令寫道:具有細微異步排序的承諾鏈

var fs = require('fs'); 

function WTest() { 
    this.writeStream = fs.createWriteStream('junkTest.txt'); 
    this.readyToWrite = Promise.resolve(true); 
}; 

WTest.prototype.writeNext = function writeNext(text, callNum) { 
    this.readyToWrite = this.readyToWrite 
    .then(status => writeToStream(text, this.writeStream, status, callNum)); 
} 

var wTest = new WTest(); 

for (var i = 0; i < 10; i++) wTest.writeNext( 
    `${i}, some line of text that might be xxxxxxxxxxxxxxxxxxxxxxxx long \n`, i); 

function writeToStream(text, writableStream, readyToWrite, callNum) { 
    return new Promise((resolve) => { 
    if (readyToWrite) resolve(writableStream.write(text)); 
    else writableStream.once('drain',() => resolve(writableStream.write(text))); 
    }); 
} 

如果你運行它是在節點7.8,它的工作原理。但在複雜的異步情況下,它會失敗,例如一行是亂序寫入的。上述代碼有什麼異步危險?你可以在失敗的地方創建一個異步的例子嗎?

我的故障實例包括多個異步文件讀取,其中上述代碼服務於記錄器功能。我已經驗證了writeNext調用在調用writeNext函數後立即將它們寫入控制檯以達​​到預期的順序。正如在評論中提到的,我寫了兩個(都直接寫入同一個文件),在promise寫入(=)後立即直接寫入「 - 」。輸出文件中的行序列是1,1 =,2,3,4,5,6,7,8,2 =,7 =,3 =,4 =,5 = 6 =,9-,9 =,10-,10 =

Bergi的大圖智慧:「防止重寫的唯一方法是停止生成新塊。所以以上只是技術上的興趣。創建承諾鏈會惡化緩衝的內存成本。

+0

「* A諾言鏈中節點不能過度寫流*有用」 - 不。你應該忽略'write'的返回值並繼續寫。當然,它會填充流的緩衝區,直到程序內存不足,但這仍然比緩衝承諾隊列中的數據更有效。 – Bergi

+0

我看到的唯一問題是,您可能會錯過'drain'事件,因爲您只有在有另一個塊寫入時纔開始收聽,而不是在您收到「false」後立即收聽,但這會導致永遠懸掛的流,而不會導致無序寫入的行。請將您的實際代碼發佈爲「*異常複雜情況*」,因爲這可能是您以意外順序調用writeNext的地方。 – Bergi

+0

@Bergi我相信我不是以意想不到的順序編寫的,因爲如果我用process.stdout.write存儲調用writeNext的輸出,則會按正確的順序生成輸出。 – Govdata1

回答

0

在輸出文件中的行序列是
1-,1 =,2-,3-,4-,5-,6-,7-,8-,2 =,J =,3 =,4 = 5 = 6 =,9 = 9,10,10 =

從你的問題的代碼不會做,當只有一個單一WTest實例,並沒有別的寫入該文件。

上述代碼有什麼異步危險?你可以在失敗的地方創建一個異步的例子嗎?

我看到的唯一問題是,你可能會錯過一個drain事件,因爲你只開始聽時,有另一塊寫的,而不是你馬上得到了false之後,但是這將導致一個永遠掛流,而不是到無序寫入的行。

更好:

function WTest(filename) { 
    this.writeStream = fs.createWriteStream(filename); 
    this.readyToWrite = Promise.resolve(); 
} 
WTest.prototype.writeNext = function writeNext(text, callNum) { 
    this.readyToWrite = this.readyToWrite.then(() => { 
    const status = writeStream.write(text); 
    if (!status) 
     return new Promise(resolve => this.writeStream.once('drain', resolve)); 
    }); 
};