# HG changeset patch # User Atul Varma # Date 1239390340 25200 # Node ID 0a694cb579ec18609e2bbc36411f68cdacf52add # Parent 8842af55f7fe68d05600252621eec9aadaf687ad map-reduce now supports 'chunking', i.e. processing some number of documents and then giving the system a chance to report progress to the user, cancel the calculation, giving the UI a chance to breathe, etc, before resuming the calculation. diff -r 8842af55f7fe -r 0a694cb579ec browser-couch.js --- a/browser-couch.js Fri Apr 10 11:32:23 2009 -0700 +++ b/browser-couch.js Fri Apr 10 12:05:40 2009 -0700 @@ -138,15 +138,20 @@ this.view = function DB_view(options) { if (!options.map) throw new Error('map function not provided'); + if (!options.finished) + throw new Error('finished callback not provided'); BrowserCouch._mapReduce(options.map, options.reduce, documents, - options.callback); + options.progress, + options.finished, + options.chunkSize); }; }, - _mapReduce: function BC__mapReduce(map, reduce, documents, cb) { + _mapReduce: function BC__mapReduce(map, reduce, documents, progress, + finished, chunkSize) { var mapResult = {}; function emit(key, value) { @@ -158,28 +163,61 @@ mapResult[key].push(value); } - for (var i = 0; i < documents.length; i++) - map(documents[i], emit); + // Maximum number of documents to process before giving the UI a chance + // to breathe. + var DEFAULT_CHUNK_SIZE = 1000; + + // If no progress callback is given, we'll automatically give the + // UI a chance to breathe for this many milliseconds before continuing + // processing. + var DEFAULT_UI_BREATHE_TIME = 50; + + if (!chunkSize) + chunkSize = DEFAULT_CHUNK_SIZE; + var i = 0; - if (reduce) { - var keys = []; - var values = []; + function continueMap() { + var iAtStart = i; - for (key in mapResult) { - keys.push(key); - values.push(mapResult[key]); + do { + map(documents[i], emit); + i++; + } while (i - iAtStart < chunkSize && + i < documents.length) + + if (i == documents.length) + doReduce(); + else { + if (progress) + progress(i / documents.length, continueMap); + else + window.setTimeout(continueMap, DEFAULT_UI_BREATHE_TIME); } + } - cb(reduce(keys, values)); - } else { - var result = []; + continueMap(); + + function doReduce() { + if (reduce) { + var keys = []; + var values = []; - for (key in mapResult) { - var values = mapResult[key]; - for (i = 0; i < values.length; i++) - result.push([key, values[i]]); + for (key in mapResult) { + keys.push(key); + values.push(mapResult[key]); + } + + finished(reduce(keys, values)); + } else { + var result = []; + + for (key in mapResult) { + var values = mapResult[key]; + for (var i = 0; i < values.length; i++) + result.push([key, values[i]]); + } + finished(result); } - cb(result); } } }; diff -r 8842af55f7fe -r 0a694cb579ec tests.js --- a/tests.js Fri Apr 10 11:32:23 2009 -0700 +++ b/tests.js Fri Apr 10 12:05:40 2009 -0700 @@ -47,12 +47,14 @@ BrowserCouch.get( "blarg", function(db) { + var progressCalled = false; db.put( [{id: "monkey", content: "hello there dude"}, {id: "chunky", content: "hello there dogen"}], function() { + var timesProgressCalled = 0; db.view( {map: function(doc, emit) { var words = doc.content.split(" "); @@ -65,7 +67,14 @@ totals[keys[i]] = values[i].length; return totals; }, - callback: function(result) { + chunkSize: 1, + progress: function(percentDone, resume) { + self.assertEqual(percentDone, 0.5); + progressCalled = true; + resume(); + }, + finished: function(result) { + self.assertEqual(progressCalled, true); self.assertEqual(result.hello, 2); self.assertEqual(result.there, 2); self.assertEqual(result.dude, 1);