From d4d7ebb78318ff74a45d5c08662ca08091654560 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 23 Sep 2013 16:35:37 -0700 Subject: [PATCH] Implements ES5-style callbacks for cursor forEach and map. Fixes #63. Based on iwoj's PR. Needs tests and docs. --- packages/minimongo/minimongo.js | 14 +++--- packages/mongo-livedata/mongo_driver.js | 60 ++++++++++++++----------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 7fb03f7810..7a00b71e2d 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -140,9 +140,8 @@ LocalCollection.prototype.findOne = function (selector, options) { return this.find(selector, options).fetch()[0]; }; -LocalCollection.Cursor.prototype.forEach = function (callback) { +LocalCollection.Cursor.prototype.forEach = function (callback, thisArg) { var self = this; - var doc; if (self.db_objects === null) self.db_objects = self._getRawObjects(true); @@ -155,12 +154,13 @@ LocalCollection.Cursor.prototype.forEach = function (callback) { movedBefore: true}); while (self.cursor_pos < self.db_objects.length) { - var elt = EJSON.clone(self.db_objects[self.cursor_pos++]); + var elt = EJSON.clone(self.db_objects[self.cursor_pos]); if (self.projection_f) elt = self.projection_f(elt); if (self._transform) elt = self._transform(elt); - callback(elt); + callback.call(thisArg, elt, self.cursor_pos, self); + ++self.cursor_pos; } }; @@ -169,11 +169,11 @@ LocalCollection.Cursor.prototype.getTransform = function () { return self._transform; }; -LocalCollection.Cursor.prototype.map = function (callback) { +LocalCollection.Cursor.prototype.map = function (callback, thisArg) { var self = this; var res = []; - self.forEach(function (doc) { - res.push(callback(doc)); + self.forEach(function (doc, index) { + res.push(callback.call(thisArg, doc, index, self)); }); return res; }; diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index deda9b9d48..aaf9d3022f 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -437,9 +437,15 @@ _.each(['forEach', 'map', 'rewind', 'fetch', 'count'], function (method) { if (self._cursorDescription.options.tailable) throw new Error("Cannot call " + method + " on a tailable cursor"); - if (!self._synchronousCursor) + if (!self._synchronousCursor) { self._synchronousCursor = self._mongo._createSynchronousCursor( - self._cursorDescription, true); + self._cursorDescription, { + // Make sure that the "self" argument to forEach/map callbacks is the + // Cursor, not the SynchronousCursor. + selfForIteration: self, + useTransform: true + }); + } return self._synchronousCursor[method].apply( self._synchronousCursor, arguments); @@ -481,20 +487,21 @@ Cursor.prototype.observeChanges = function (callbacks) { self._cursorDescription, ordered, callbacks); }; -MongoConnection.prototype._createSynchronousCursor = function(cursorDescription, - useTransform) { +MongoConnection.prototype._createSynchronousCursor = function( + cursorDescription, options) { var self = this; + options = _.pick(options || {}, 'selfForIteration', 'useTransform'); var collection = self._getCollection(cursorDescription.collectionName); - var options = cursorDescription.options; + var cursorOptions = cursorDescription.options; var mongoOptions = { - sort: options.sort, - limit: options.limit, - skip: options.skip + sort: cursorOptions.sort, + limit: cursorOptions.limit, + skip: cursorOptions.skip }; // Do we want a tailable cursor (which only works on capped collections)? - if (options.tailable) { + if (cursorOptions.tailable) { // We want a tailable cursor... mongoOptions.tailable = true; // ... and for the server to wait a bit if any getMore has no data (rather @@ -507,16 +514,21 @@ MongoConnection.prototype._createSynchronousCursor = function(cursorDescription, var dbCursor = collection.find( replaceTypes(cursorDescription.selector, replaceMeteorAtomWithMongo), - options.fields, mongoOptions); + cursorOptions.fields, mongoOptions); - return new SynchronousCursor(dbCursor, cursorDescription, useTransform); + return new SynchronousCursor(dbCursor, cursorDescription, options); }; -var SynchronousCursor = function (dbCursor, cursorDescription, useTransform) { +var SynchronousCursor = function (dbCursor, cursorDescription, options) { var self = this; + options = _.pick(options || {}, 'selfForIteration', 'useTransform'); + self._dbCursor = dbCursor; self._cursorDescription = cursorDescription; - if (useTransform && cursorDescription.options.transform) { + // The "self" argument passed to forEach/map callbacks. If we're wrapped + // inside a user-visible Cursor, we want to provide the outer cursor! + self._selfForIteration = options.selfForIteration || self; + if (options.useTransform && cursorDescription.options.transform) { self._transform = Deps._makeNonreactive( cursorDescription.options.transform ); @@ -558,29 +570,26 @@ _.extend(SynchronousCursor.prototype, { } }, - // XXX Make more like ECMA forEach: - // https://github.com/meteor/meteor/pull/63#issuecomment-5320050 - forEach: function (callback) { + forEach: function (callback, thisArg) { var self = this; // We implement the loop ourself instead of using self._dbCursor.each, // because "each" will call its callback outside of a fiber which makes it // much more complex to make this function synchronous. + var index = 0; while (true) { var doc = self._nextObject(); if (!doc) return; - callback(doc); + callback.call(thisArg, doc, index++, self._selfForIteration); } }, - // XXX Make more like ECMA map: - // https://github.com/meteor/meteor/pull/63#issuecomment-5320050 // XXX Allow overlapping callback executions if callback yields. - map: function (callback) { + map: function (callback, thisArg) { var self = this; var res = []; - self.forEach(function (doc) { - res.push(callback(doc)); + self.forEach(function (doc, index) { + res.push(callback.call(thisArg, doc, index, self._selfForIteration)); }); return res; }, @@ -892,7 +901,7 @@ _.extend(LiveResultsSet.prototype, { self._synchronousCursor.rewind(); } else { self._synchronousCursor = self._mongoHandle._createSynchronousCursor( - self._cursorDescription, false /* !useTransform */); + self._cursorDescription); } var newResults = self._synchronousCursor.getRawObjects(self._ordered); var oldResults = self._results; @@ -1020,8 +1029,7 @@ MongoConnection.prototype._observeChangesTailable = function ( + " tailable cursor without a " + (ordered ? "addedBefore" : "added") + " callback"); } - var cursor = self._createSynchronousCursor(cursorDescription, - false /* useTransform */); + var cursor = self._createSynchronousCursor(cursorDescription); var stopped = false; var lastTS = undefined; @@ -1063,7 +1071,7 @@ MongoConnection.prototype._observeChangesTailable = function ( cursor = self._createSynchronousCursor(new CursorDescription( cursorDescription.collectionName, newSelector, - cursorDescription.options), false /* useTransform */); + cursorDescription.options)); } } });