Implements ES5-style callbacks for cursor forEach and map.

Fixes #63.

Based on iwoj's PR.

Needs tests and docs.
This commit is contained in:
David Glasser
2013-09-23 16:35:37 -07:00
parent 3177d9ad41
commit d4d7ebb783
2 changed files with 41 additions and 33 deletions

View File

@@ -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;
};

View File

@@ -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));
}
}
});