Skip to content

Commit ed977f2

Browse files
authored
feat(Query): #scan (#437)
1 parent 42e9676 commit ed977f2

File tree

2 files changed

+134
-14
lines changed

2 files changed

+134
-14
lines changed

src/query.js

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const _ = require('underscore');
2+
const debug = require('debug')('leancloud:query');
3+
const Promise = require('./promise');
24
const AVError = require('./error');
35
const AVRequest = require('./request').request;
46
const { ensureArray } = require('./utils');
@@ -263,27 +265,107 @@ module.exports = function(AV) {
263265
return AVRequest('classes', this.className, null, "GET", params, options);
264266
},
265267

268+
_parseResponse(response) {
269+
return _.map(response.results, (json) => {
270+
var obj = this._newObject(response);
271+
if (obj._finishFetch) {
272+
obj._finishFetch(this._processResult(json), true);
273+
}
274+
return obj;
275+
});
276+
},
277+
266278
/**
267279
* Retrieves a list of AVObjects that satisfy this query.
268280
*
269281
* @param {AuthOptions} options
270282
* @return {Promise} A promise that is resolved with the results when
271283
* the query completes.
272284
*/
273-
find: function(options) {
274-
var self = this;
275-
276-
var request = this._createRequest(undefined, options);
285+
find(options) {
286+
const request = this._createRequest(undefined, options);
287+
return request.then(this._parseResponse.bind(this));
288+
},
277289

278-
return request.then(function(response) {
279-
return _.map(response.results, function(json) {
280-
var obj = self._newObject(response);
281-
if (obj._finishFetch) {
282-
obj._finishFetch(self._processResult(json), true);
283-
}
284-
return obj;
285-
});
286-
});
290+
/**
291+
* scan a Query. masterKey required.
292+
*
293+
* @param {String} orderedBy
294+
* @param {Number} batchSize
295+
* @return {AsyncIterator.<AV.Object>}
296+
* @example const scan = new AV.Query(TestClass).scan({
297+
* orderedBy: 'objectId',
298+
* batchSize: 10,
299+
* }, {
300+
* useMasterKey: true,
301+
* });
302+
* const getTen = () => Promise.all(new Array(10).fill(0).map(() => scan.next()));
303+
* getTen().then(results => {
304+
* // results are fisrt 10 instances of TestClass
305+
* return getTen();
306+
* }).then(results => {
307+
* // 11 - 20
308+
* });
309+
*/
310+
scan({
311+
orderedBy,
312+
batchSize,
313+
} = {}, authOptions) {
314+
const condition = this.toJSON();
315+
debug('scan %O', condition);
316+
if (condition.order) {
317+
console.warn('The order of the query is ignored for Query#scan. Checkout the orderedBy option of Query#scan.');
318+
delete condition.order;
319+
}
320+
if (condition.skip) {
321+
console.warn('The skip option of the query is ignored for Query#scan.');
322+
delete condition.skip;
323+
}
324+
if (condition.limit) {
325+
console.warn('The limit option of the query is ignored for Query#scan.');
326+
delete condition.limit;
327+
}
328+
if (orderedBy) condition.scan_key = orderedBy;
329+
if (batchSize) condition.limit = batchSize;
330+
let promise = Promise.resolve([]);
331+
let cursor;
332+
let done = false;
333+
return {
334+
next: () => {
335+
promise = promise.then((remainResults) => {
336+
if (done) return [];
337+
if (remainResults.length > 1) return remainResults;
338+
// no cursor means we have reached the end
339+
// except for the first time
340+
if (!cursor && remainResults.length !== 0) {
341+
done = true;
342+
return remainResults;
343+
}
344+
// when only 1 item left in queue
345+
// start the next request to see if it is the last one
346+
return AVRequest(
347+
'scan/classes',
348+
this.className,
349+
null,
350+
'GET',
351+
cursor ? _.extend({}, condition, { cursor }) : condition,
352+
authOptions
353+
).then(response => {
354+
cursor = response.cursor;
355+
return this._parseResponse(response);
356+
}).then(results => {
357+
if (!results.length) done = true;
358+
return remainResults.concat(results);
359+
});
360+
});
361+
return promise
362+
.then(remainResults => remainResults.shift())
363+
.then(result => ({
364+
value: result,
365+
done,
366+
}));
367+
},
368+
};
287369
},
288370

289371
/**

test/query.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ describe('Queries', function () {
187187
expect(gameScore.get('playerName')).to.be(undefined);
188188
});
189189
});
190-
190+
191191
it('include with multi params', function() {
192192
return new AV.Query(GameScore)
193193
.include('score', 'test')
@@ -381,4 +381,42 @@ describe('Queries', function () {
381381
});
382382
});
383383

384+
describe('scan', () => {
385+
const now = Date.now();
386+
before(function () {
387+
this.savePromise = AV.Object.saveAll(
388+
(new Array(4).fill(now)).map(ts => new TestClass().set('timestamp', ts))
389+
);
390+
return this.savePromise;
391+
});
392+
393+
after(function () {
394+
return this.savePromise.then(AV.Object.destroyAll);
395+
});
396+
397+
it('should works', () => {
398+
const scan = new AV.Query(TestClass).equalTo('timestamp', now).scan({
399+
orderedBy: 'objectId',
400+
batchSize: 2,
401+
}, {
402+
useMasterKey: true,
403+
});
404+
return scan.next().then(({ value, done }) => {
405+
value.should.be.instanceof(TestClass);
406+
done.should.be.false();
407+
}).then(() => scan.next()).then(({ value, done }) => {
408+
value.should.be.instanceof(TestClass);
409+
done.should.be.false();
410+
}).then(() => scan.next()).then(({ value, done }) => {
411+
value.should.be.instanceof(TestClass);
412+
done.should.be.false();
413+
}).then(() => scan.next()).then(({ value, done }) => {
414+
value.should.be.instanceof(TestClass);
415+
done.should.be.true();
416+
}).then(() => scan.next()).then(({ value, done }) => {
417+
expect(value).to.eql(undefined);
418+
done.should.be.true();
419+
});
420+
});
421+
});
384422
});

0 commit comments

Comments
 (0)