之前的一篇文章中介绍了在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
网友评论