arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/querycursor.js
node_modules/mongoose/lib/querycursor.jsRaw
  1/*!
  2 * Module dependencies.
  3 */
  4
  5var PromiseProvider = require('./promise_provider');
  6var Readable = require('stream').Readable;
  7var helpers = require('./queryhelpers');
  8var util = require('util');
  9
 10/**
 11 * A QueryCursor is a concurrency primitive for processing query results
 12 * one document at a time. A QueryCursor fulfills the [Node.js streams3 API](https://strongloop.com/strongblog/whats-new-io-js-beta-streams3/),
 13 * in addition to several other mechanisms for loading documents from MongoDB
 14 * one at a time.
 15 *
 16 * Unless you're an advanced user, do **not** instantiate this class directly.
 17 * Use [`Query#cursor()`](/api.html#query_Query-cursor) instead.
 18 *
 19 * @param {Query} query
 20 * @param {Object} options query options passed to `.find()`
 21 * @inherits Readable
 22 * @event `cursor`: Emitted when the cursor is created
 23 * @event `error`: Emitted when an error occurred
 24 * @event `data`: Emitted when the stream is flowing and the next doc is ready
 25 * @event `end`: Emitted when the stream is exhausted
 26 * @api public
 27 */
 28
 29function QueryCursor(query, options) {
 30  Readable.call(this, { objectMode: true });
 31
 32  this.cursor = null;
 33  this.query = query;
 34  var _this = this;
 35  var model = query.model;
 36  model.collection.find(query._conditions, options, function(err, cursor) {
 37    if (_this._error) {
 38      cursor.close(function() {});
 39      _this.listeners('error').length > 0 && _this.emit('error', _this._error);
 40    }
 41    if (err) {
 42      return _this.emit('error', err);
 43    }
 44    _this.cursor = cursor;
 45    _this.emit('cursor', cursor);
 46  });
 47}
 48
 49util.inherits(QueryCursor, Readable);
 50
 51/*!
 52 * Necessary to satisfy the Readable API
 53 */
 54
 55QueryCursor.prototype._read = function() {
 56  var _this = this;
 57  _next(this, function(error, doc) {
 58    if (error) {
 59      return _this.emit('error', error);
 60    }
 61    if (!doc) {
 62      _this.push(null);
 63      return _this.cursor.close(function(error) {
 64        if (error) {
 65          return _this.emit('error', error);
 66        }
 67        _this.emit('close');
 68      });
 69    }
 70    _this.push(doc);
 71  });
 72};
 73
 74/*!
 75 * Marks this cursor as errored
 76 */
 77
 78QueryCursor.prototype._markError = function(error) {
 79  this._error = error;
 80  return this;
 81};
 82
 83/**
 84 * Marks this cursor as closed. Will stop streaming and subsequent calls to
 85 * `next()` will error.
 86 *
 87 * @param {Function} callback
 88 * @return {Promise}
 89 * @api public
 90 * @method close
 91 * @emits close
 92 * @see MongoDB driver cursor#close http://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html#close
 93 */
 94
 95QueryCursor.prototype.close = function(callback) {
 96  var Promise = PromiseProvider.get();
 97  var _this = this;
 98  return new Promise.ES6(function(resolve, reject) {
 99    _this.cursor.close(function(error) {
100      if (error) {
101        callback && callback(error);
102        reject(error);
103        return _this.listeners('error').length > 0 &&
104          _this.emit('error', error);
105      }
106      _this.emit('close');
107      resolve();
108      callback && callback();
109    });
110  });
111};
112
113/**
114 * Get the next document from this cursor. Will return `null` when there are
115 * no documents left.
116 *
117 * @param {Function} callback
118 * @return {Promise}
119 * @api public
120 * @method next
121 */
122
123QueryCursor.prototype.next = function(callback) {
124  var Promise = PromiseProvider.get();
125  var _this = this;
126  return new Promise.ES6(function(resolve, reject) {
127    _next(_this, function(error, doc) {
128      if (error) {
129        callback && callback(error);
130        return reject(error);
131      }
132      callback && callback(null, doc);
133      resolve(doc);
134    });
135  });
136};
137
138/**
139 * Execute `fn` for every document in the cursor. If `fn` returns a promise,
140 * will wait for the promise to resolve before iterating on to the next one.
141 * Returns a promise that resolves when done.
142 *
143 * @param {Function} fn
144 * @param {Function} [callback] executed when all docs have been processed
145 * @return {Promise}
146 * @api public
147 * @method eachAsync
148 */
149
150QueryCursor.prototype.eachAsync = function(fn, callback) {
151  var Promise = PromiseProvider.get();
152  var _this = this;
153
154  var handleNextResult = function(doc, callback) {
155    var promise = fn(doc);
156    if (promise && typeof promise.then === 'function') {
157      promise.then(
158        function() { callback(null); },
159        function(error) { callback(error); });
160    } else {
161      callback(null);
162    }
163  };
164
165  var iterate = function(callback) {
166    return _next(_this, function(error, doc) {
167      if (error) {
168        return callback(error);
169      }
170      if (!doc) {
171        return callback(null);
172      }
173      handleNextResult(doc, function(error) {
174        if (error) {
175          return callback(error);
176        }
177        iterate(callback);
178      });
179    });
180  };
181
182  return new Promise.ES6(function(resolve, reject) {
183    iterate(function(error) {
184      if (error) {
185        callback && callback(error);
186        return reject(error);
187      }
188      callback && callback(null);
189      return resolve();
190    });
191  });
192};
193
194/*!
195 * Get the next doc from the underlying cursor and mongooseify it
196 * (populate, etc.)
197 */
198
199function _next(ctx, callback) {
200  if (ctx._error) {
201    return process.nextTick(function() {
202      callback(ctx._error);
203    });
204  }
205
206  if (ctx.cursor) {
207    ctx.cursor.next(function(error, doc) {
208      if (error) {
209        return callback(error);
210      }
211      if (!doc) {
212        return callback(null, null);
213      }
214
215      var opts = ctx.query._mongooseOptions;
216      if (!opts.populate) {
217        return opts.lean === true ?
218                callback(null, doc) :
219                _create(ctx, doc, null, callback);
220      }
221
222      var pop = helpers.preparePopulationOptionsMQ(ctx.query,
223        ctx.query._mongooseOptions);
224      pop.forEach(function(option) {
225        delete option.model;
226      });
227      pop.__noPromise = true;
228      ctx.query.model.populate(doc, pop, function(err, doc) {
229        if (err) {
230          return callback(err);
231        }
232        return opts.lean === true ?
233          callback(null, doc) :
234          _create(ctx, doc, pop, callback);
235      });
236    });
237  } else {
238    ctx.once('cursor', function() {
239      _next(ctx, callback);
240    });
241  }
242}
243
244/*!
245 * Convert a raw doc into a full mongoose doc.
246 */
247
248function _create(ctx, doc, populatedIds, cb) {
249  var instance = helpers.createModel(ctx.query.model, doc, ctx.query._fields);
250  var opts = populatedIds ?
251    { populated: populatedIds } :
252    undefined;
253
254  instance.init(doc, opts, function(err) {
255    if (err) {
256      return cb(err);
257    }
258    cb(null, instance);
259  });
260}
261
262module.exports = QueryCursor;