之前的一篇文章中介紹了在Nodejs下如何實現郵件的同步讀取【1】,基於imap,對與庫中的方法用wrap進行了包裝,實現了同步行。但是,從代碼的實現來看,仍然存在着兩個問題。首先是實現同步關閉連接的方法必須和讀取郵件的“配對使用”。 其次,當多次調用讀取郵件的方法時候,會引起emitter MaxListeners error。
對於第一個問題可以修改結束imap連接的方法,使其在郵件讀取和解析完成之後執行,也就是說作爲回調函數放入promise鏈:
async function endAsync(message = null) { return new Promise(function (resolve, nay) { imap.on('end', function () { resolve(message); }); imap.end(); });}
…
).then(function (messages) { return endAsync(messages[messages.length - 1]);}).then(function (message) { return message;}) .catch(function (error) { console.error("Oops:", error.message); imap.end(); })
對於第二個問題,錯誤的信息是:
(node:3133) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 end listeners added. Use emitter.setMaxListeners() to increase limit
在nodejs的官方文檔上可以找到相應的說明:
By default EventEmitters will print a warning if more than 10 listeners are added for a particular event. This is a useful default that helps finding memory leaks.
默認情況下,當爲同一個事件添加了超過10個listener的時候,就會打印出一條警告信息。這一默認行爲有助於發現內存泄漏。
正向我之前的一篇文章【2】中講到的,Javascript中,javascript runtime還維護着第三類內存空間,事件隊列(Queue),因此不得不將事件的listener考慮到內存管理的範疇之內。但是並不想通過增加允許的listener數量來回避這個問題,因爲有一種更好的辦法。
Using the eventEmitter.once() method, it is possible to register a listener that is called at most once for a particular event. Once the event is emitted, the listener is unregistered and then called.
當用once註冊listener的時候,它只會爲一個特定的事件調用一次,一旦事件觸發了,listener同樣會被觸發,但是之後就會被刪除。
async function endAsync(message = null) { return new Promise(function (resolve, nay) { imap.once('end', function () { resolve(message); }); imap.end(); });}
解決了這兩個問題之後,回看這個簡單的例子不難發現,imap對象是一個全局變量,或者說是一個singleton模式的應用,所以纔會有setMaxListeners的問題。
const imap = new Imap({ user: 'user', password: 'password', port: 993, host: 'host', tls: true});
通過這兩篇文章介紹,讀者應該可以瞭解我是如何實現Nodejs下郵件的同步讀取。並且,文章描述了在這一過程中出現的問題和相應的解決方法。
實例代碼可以在github上找到【3】。
【1】Nodejs下實現郵件的同步讀取 1
【2】Nodejs Event Loop
【3】test_imap_sample_end_once.js