Skip to content

Commit d3ab1ce

Browse files
authored
Support query.map, query.filter, query.reduce (#987)
* Support query.map() * nit * filter and reduce
1 parent 569b12a commit d3ab1ce

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed

integration/test/ParseQueryTest.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,4 +1947,55 @@ describe('Parse Query', () => {
19471947
done();
19481948
});
19491949
});
1950+
1951+
it('can return results in map', async () => {
1952+
const obj1 = new TestObject({ foo: 'bar' });
1953+
const obj2 = new TestObject({ foo: 'baz' });
1954+
const obj3 = new TestObject({ foo: 'bin' });
1955+
await Parse.Object.saveAll([obj1, obj2, obj3]);
1956+
let i = 0;
1957+
const mapQuery = new Parse.Query(TestObject);
1958+
const results = await mapQuery.map((object, index, query) => {
1959+
assert.equal(index, i);
1960+
assert.equal(query, mapQuery);
1961+
i += 1;
1962+
return object.get('foo');
1963+
});
1964+
assert.equal(results.includes('bar'), true);
1965+
assert.equal(results.includes('baz'), true);
1966+
assert.equal(results.includes('bin'), true);
1967+
assert.equal(results.length, 3);
1968+
});
1969+
1970+
it('can return results in filter', async () => {
1971+
const obj1 = new TestObject({ foo: 'bar' });
1972+
const obj2 = new TestObject({ foo: 'baz' });
1973+
const obj3 = new TestObject({ foo: 'bin' });
1974+
await Parse.Object.saveAll([obj1, obj2, obj3]);
1975+
let i = 0;
1976+
const filterQuery = new Parse.Query(TestObject);
1977+
const results = await filterQuery.filter((object, index, query) => {
1978+
assert.equal(index, i);
1979+
assert.equal(query, filterQuery);
1980+
i += 1;
1981+
return object.get('foo') === 'bar';
1982+
});
1983+
assert.equal(results[0].get('foo'), 'bar');
1984+
assert.equal(results.length, 1);
1985+
});
1986+
1987+
it('can return results in reduce', async () => {
1988+
const obj1 = new TestObject({ number: 1 });
1989+
const obj2 = new TestObject({ number: 2 });
1990+
const obj3 = new TestObject({ number: 3 });
1991+
await Parse.Object.saveAll([obj1, obj2, obj3]);
1992+
let i = 0;
1993+
const reduceQuery = new Parse.Query(TestObject);
1994+
const result = await reduceQuery.reduce((accumulator, object, index) => {
1995+
assert.equal(index, i);
1996+
i += 1;
1997+
return accumulator + object.get('number');
1998+
}, 0);
1999+
assert.equal(result, 6);
2000+
});
19502001
});

src/ParseQuery.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,103 @@ class ParseQuery {
910910
});
911911
}
912912

913+
/**
914+
* Iterates over each result of a query, calling a callback for each one. If
915+
* the callback returns a promise, the iteration will not continue until
916+
* that promise has been fulfilled. If the callback returns a rejected
917+
* promise, then iteration will stop with that error. The items are
918+
* processed in an unspecified order. The query may not have any sort order,
919+
* and may not use limit or skip.
920+
* @param {Function} callback Callback <ul>
921+
* <li>currentObject: The current Parse.Object being processed in the array.</li>
922+
* <li>index: The index of the current Parse.Object being processed in the array.</li>
923+
* <li>query: The query map was called upon.</li>
924+
* </ul>
925+
*
926+
* @param {Object} options Valid options are:<ul>
927+
* <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
928+
* be used for this request.
929+
* <li>sessionToken: A valid session token, used for making a request on
930+
* behalf of a specific user.
931+
* </ul>
932+
* @return {Promise} A promise that will be fulfilled once the
933+
* iteration has completed.
934+
*/
935+
async map(callback: (currentObject: ParseObject, index: number, query: ParseQuery) => any, options?: BatchOptions): Promise<Array<any>> {
936+
const array = [];
937+
let index = 0;
938+
await this.each((object) => {
939+
array.push(callback(object, index, this));
940+
index += 1;
941+
}, options);
942+
return array;
943+
}
944+
945+
/**
946+
* Iterates over each result of a query, calling a callback for each one. If
947+
* the callback returns a promise, the iteration will not continue until
948+
* that promise has been fulfilled. If the callback returns a rejected
949+
* promise, then iteration will stop with that error. The items are
950+
* processed in an unspecified order. The query may not have any sort order,
951+
* and may not use limit or skip.
952+
* @param {Function} callback Callback <ul>
953+
* <li>accumulator: The accumulator accumulates the callback's return values. It is the accumulated value previously returned in the last invocation of the callback.</li>
954+
* <li>currentObject: The current Parse.Object being processed in the array.</li>
955+
* <li>index: The index of the current Parse.Object being processed in the array.</li>
956+
* </ul>
957+
* @param {Mixed} initialValue A value to use as the first argument to the first call of the callback. If no initialValue is supplied, the first object in the query will be used and skipped.
958+
* @param {Object} options Valid options are:<ul>
959+
* <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
960+
* be used for this request.
961+
* <li>sessionToken: A valid session token, used for making a request on
962+
* behalf of a specific user.
963+
* </ul>
964+
* @return {Promise} A promise that will be fulfilled once the
965+
* iteration has completed.
966+
*/
967+
async reduce(callback: (accumulator: any, currentObject: ParseObject, index: number) => any, initialValue: any, options?: BatchOptions): Promise<Array<any>> {
968+
const objects = [];
969+
await this.each((object) => {
970+
objects.push(object);
971+
}, options);
972+
return objects.reduce(callback, initialValue);
973+
}
974+
975+
/**
976+
* Iterates over each result of a query, calling a callback for each one. If
977+
* the callback returns a promise, the iteration will not continue until
978+
* that promise has been fulfilled. If the callback returns a rejected
979+
* promise, then iteration will stop with that error. The items are
980+
* processed in an unspecified order. The query may not have any sort order,
981+
* and may not use limit or skip.
982+
* @param {Function} callback Callback <ul>
983+
* <li>currentObject: The current Parse.Object being processed in the array.</li>
984+
* <li>index: The index of the current Parse.Object being processed in the array.</li>
985+
* <li>query: The query filter was called upon.</li>
986+
* </ul>
987+
*
988+
* @param {Object} options Valid options are:<ul>
989+
* <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
990+
* be used for this request.
991+
* <li>sessionToken: A valid session token, used for making a request on
992+
* behalf of a specific user.
993+
* </ul>
994+
* @return {Promise} A promise that will be fulfilled once the
995+
* iteration has completed.
996+
*/
997+
async filter(callback: (currentObject: ParseObject, index: number, query: ParseQuery) => boolean, options?: BatchOptions): Promise<Array<ParseObject>> {
998+
const array = [];
999+
let index = 0;
1000+
await this.each((object) => {
1001+
const flag = callback(object, index, this);
1002+
if (flag) {
1003+
array.push(object);
1004+
}
1005+
index += 1;
1006+
}, options);
1007+
return array;
1008+
}
1009+
9131010
/** Query Conditions **/
9141011

9151012
/**

src/__tests__/ParseQuery-test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,66 @@ describe('ParseQuery', () => {
15801580
});
15811581
});
15821582

1583+
it('can iterate over results with map()', async () => {
1584+
CoreManager.setQueryController({
1585+
aggregate() {},
1586+
find() {
1587+
return Promise.resolve({
1588+
results: [
1589+
{ objectId: 'I55', size: 'medium', name: 'Product 55' },
1590+
{ objectId: 'I89', size: 'small', name: 'Product 89' },
1591+
{ objectId: 'I91', size: 'small', name: 'Product 91' },
1592+
]
1593+
});
1594+
}
1595+
});
1596+
1597+
const q = new ParseQuery('Item');
1598+
1599+
const results = await q.map((object) => object.attributes.size);
1600+
expect(results.length).toBe(3);
1601+
});
1602+
1603+
it('can iterate over results with reduce()', async () => {
1604+
CoreManager.setQueryController({
1605+
aggregate() {},
1606+
find() {
1607+
return Promise.resolve({
1608+
results: [
1609+
{ objectId: 'I55', number: 1 },
1610+
{ objectId: 'I89', number: 2 },
1611+
{ objectId: 'I91', number: 3 },
1612+
]
1613+
});
1614+
}
1615+
});
1616+
1617+
const q = new ParseQuery('Item');
1618+
1619+
const result = await q.reduce((accumulator, object) => accumulator + object.attributes.number, 0);
1620+
expect(result).toBe(6);
1621+
});
1622+
1623+
it('can iterate over results with filter()', async () => {
1624+
CoreManager.setQueryController({
1625+
aggregate() {},
1626+
find() {
1627+
return Promise.resolve({
1628+
results: [
1629+
{ objectId: 'I55', size: 'medium', name: 'Product 55' },
1630+
{ objectId: 'I89', size: 'small', name: 'Product 89' },
1631+
{ objectId: 'I91', size: 'small', name: 'Product 91' },
1632+
]
1633+
});
1634+
}
1635+
});
1636+
1637+
const q = new ParseQuery('Item');
1638+
1639+
const results = await q.filter((object) => object.attributes.size === 'small');
1640+
expect(results.length).toBe(2);
1641+
});
1642+
15831643
it('returns an error when iterating over an invalid query', (done) => {
15841644
const q = new ParseQuery('Item');
15851645
q.limit(10);

0 commit comments

Comments
 (0)