使用数据库查找在节点中导出大文件 – 避免多个数据库调用?

我是相当新的节点,虽然我认为这是伟大的服务types的应用程序,我使用它的应用程序时,只需像数据导出应用程序运行时,需要callback是访问数据库或其他这样的事情。

这是我目前的设置。

我有一个脚本,可以将数据从MongoDB导出到XML文件中,以便在单独的进程中使用。 导出脚本非常简单:

db.getData(function(err, data) { data.forEach(function(entry) { // write the data to the file writeData(entry); }); }); 

问题是当我需要在导出期间进行非同步调用时,例如:

 db.getData(function(err, data) { data.forEach(function(entry) { var cacheValue = cache.get(entry.someOtherId); if (cacheValue) { // write the value from the cache writeData(entry, cacheValue); } else { // THIS IS CALLED 1000's OF TIMES EVEN THOUGH THE FIRST FEW CALLS // SHOULD POPULATE THE CACHE db.getLookup(entry.someOtherId, function(err, value) { // store it in the cache to avoid db calls cache.store(entry.someOtherId, value); // write the data to the file after getting the lookup writeData(entry, value); }); } }); }); 

由于执行getLookup时节点的非阻塞特性,主forEach循环将继续,并且因为entry.someOtherId字段是查找,通常它将包含与另一个logging相同的值。

所以会发生的是,对于一个查找量相对较less的大文件,在第一个数据库有机会返回并将值存储在caching之前,我将成千上万的数据库调用发送到getLookup

预装不需要

我知道我可以简单地重新加载caching,因为查找表是相当小的,但是如何处理更大的查找,重新caching所有的值是不切实际的呢?

暂停主循环

在同步环境中,这将很简单,主循环将停止,直到DB值被返回,所以下一次该值已经在caching中。

我知道有各种库试图停止线程执行,直到callback返回,但似乎违背什么节点。

有人能告诉我在Node中处理这种情况的普遍接受的模式是什么?

我推荐使用promise库和memoization函数来解决处理并行运行的多个asynchronous操作的任务。

对于以下示例,我正在使用蓝鸟。 包括结果caching在内的整个循环可以简化为这样一个相当清晰的代码:

 var db = Promise.promisifyAll(db); var lookup = memoize(db.getLookupAsync, db); entries.forEach(function (entry) { lookup(entry.someOtherId).then(function (value) { writeData(entry, value); }); }); 

memoize是caching函数结果的通用帮助函数:

 function memoize(func, thisArg) { var cache = {}; return function memoize(id) { if (!cache.hasOwnProperty(id)) { cache[id] = func.apply(thisArg || this, arguments); } return cache[id]; }; } 

所以lookup()是一个函数,它调用db.getLookup()promisified版本(bluebird的.promisifyAll()创build一个对象中所有函数的...Async()版本)并logging相应的结果。

promisified函数返回一个承诺,解决(即调用其.then()callback)一旦数据可用,或立即(如果它已经解决了之前)。 换句话说,我们可以caching一个承诺,并尽可能经常地调用.then()

有了这个设置,我们就可以解决处理asynchronous函数调用的任务,同时caching结果以尽可能快地保持进程。 此外,它是愉快的,直接阅读,而不是“回拨地狱”。

看看http://jsfiddle.net/Tomalak/91bdb5ns/ ,你可以看到它的工作。

请注意,我的代码中没有error handling。 你应该读入蓝鸟文档并自己添加。

我想我现在真的明白expressioncallback hell

事实certificate(事实上并不令人惊讶),这需要全部在callback函数和recursion函数中完成,所以下一个条目在上一个条目完成之前不会启动:

使用此处描述的方法: 使用带有node.js的recursion模式循环

处理值的数组时,数组与索引一起被传递给一个函数,当该索引的值已被处理时,它将使用index + 1的索引来调用它自己:

 function processEntry(entries, index, next) { // no more entries to run if (index >= entries.length) { next(); return; } var cacheValue = cache.get(entry.someOtherId); if (cacheValue) { // write the value from the cache writeData(entry, cacheValue); // process the next entry process.nextTick(function() { processEntry(entries, index+1, next); }); } else { db.getLookup(entry.someOtherId, function(err, value) { // store it in the cache to avoid db calls cache.store(entry.someOtherId, value); // write the data to the file after getting the lookup writeData(entry, value); // process the next entry processEntry(entries, index+1, next); }); } } 

避免堆栈溢出

这个设置的问题是,一旦caching被填充,我们将开始直接在processEntry调用processEntry ,而不是从不同的callback堆栈中调用,所以不久之后我们会得到堆栈溢出。

为了避免这种情况,我们需要通过使用process.nextTick() http://nodejs.org/api/process.html#process_process_nexttick_callback来告诉Node创build一个新的堆栈

在事件循环的下一个循环中调用这个callback函数。 这不是setTimeout(fn,0)的简单别名,它更有效率。 它通常在任何其他I / O事件触发之前运行,但也有一些例外。 请参阅下面的process.maxTickDepth。

根据文档,这个调用是相当有效的