Skip to content

Commit 16d6572

Browse files
author
Thomas Reggi
authored
feat!: adds async iterator for custom promises
NODE-2590
1 parent 8aad134 commit 16d6572

File tree

3 files changed

+127
-73
lines changed

3 files changed

+127
-73
lines changed

src/cursor/cursor.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CountOperation, CountOptions } from '../operations/count';
99
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
1010
import { Callback, emitDeprecatedOptionWarning, maybePromise, MongoDBNamespace } from '../utils';
1111
import { Sort, SortDirection, formatSort } from '../sort';
12+
import { PromiseProvider } from '../promise_provider';
1213
import type { OperationTime, ResumeToken } from '../change_stream';
1314
import type { CloseOptions } from '../cmap/connection_pool';
1415
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
@@ -1227,7 +1228,7 @@ export class Cursor<
12271228
}
12281229

12291230
/** Close the cursor, sending a KillCursor command and emitting close. */
1230-
close(): void;
1231+
close(): Promise<void>;
12311232
close(callback: Callback): void;
12321233
close(options: CursorCloseOptions): Promise<void>;
12331234
close(options: CursorCloseOptions, callback: Callback): void;
@@ -1320,6 +1321,17 @@ export class Cursor<
13201321
return this.logger;
13211322
}
13221323

1324+
[Symbol.asyncIterator](): AsyncIterator<Document> {
1325+
const Promise = PromiseProvider.get();
1326+
return {
1327+
next: () => {
1328+
if (this.isClosed()) {
1329+
return Promise.resolve({ value: null, done: true });
1330+
}
1331+
return this.next().then(value => ({ value, done: value === null }));
1332+
}
1333+
};
1334+
}
13231335
// Internal methods
13241336

13251337
/** @internal */

src/mongo_client.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ export class MongoClient extends EventEmitter implements OperationParent {
273273

274274
if (options && options.promiseLibrary) {
275275
PromiseProvider.set(options.promiseLibrary);
276+
// TODO NODE-2530: this will go away when client options are sorted out
277+
// NOTE: need this to prevent deprecation notice from being inherited in Db, Collection
278+
delete options.promiseLibrary;
276279
}
277280

278281
// The internal state
@@ -440,10 +443,6 @@ export class MongoClient extends EventEmitter implements OperationParent {
440443
if (typeof options === 'function') (callback = options), (options = {});
441444
options = options || {};
442445

443-
if (options && options.promiseLibrary) {
444-
PromiseProvider.set(options.promiseLibrary);
445-
}
446-
447446
// Create client
448447
const mongoClient = new MongoClient(url, options);
449448
// Execute the connect method
Lines changed: 111 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,134 @@
11
'use strict';
22

33
const { expect } = require('chai');
4+
const Sinon = require('sinon');
45

5-
// TODO: unskip as part of NODE-2590
6-
describe.skip('Cursor Async Iterator Tests', function () {
7-
let client, collection;
8-
before(async function () {
9-
client = this.configuration.newClient();
6+
describe('Cursor Async Iterator Tests', function () {
7+
context('default promise library', function () {
8+
let client, collection;
9+
before(async function () {
10+
client = this.configuration.newClient();
1011

11-
await client.connect();
12-
const docs = Array.from({ length: 1000 }).map((_, index) => ({ foo: index, bar: 1 }));
12+
await client.connect();
13+
const docs = Array.from({ length: 1000 }).map((_, index) => ({ foo: index, bar: 1 }));
1314

14-
collection = client.db(this.configuration.db).collection('async_cursor_tests');
15+
collection = client.db(this.configuration.db).collection('async_cursor_tests');
1516

16-
await collection.deleteMany({});
17-
await collection.insertMany(docs);
18-
await client.close();
19-
});
17+
await collection.deleteMany({});
18+
await collection.insertMany(docs);
19+
await client.close();
20+
});
2021

21-
beforeEach(async function () {
22-
client = this.configuration.newClient();
23-
await client.connect();
24-
collection = client.db(this.configuration.db).collection('async_cursor_tests');
25-
});
22+
beforeEach(async function () {
23+
client = this.configuration.newClient();
24+
await client.connect();
25+
collection = client.db(this.configuration.db).collection('async_cursor_tests');
26+
});
27+
28+
afterEach(() => client.close());
2629

27-
afterEach(() => client.close());
30+
it('should be able to use a for-await loop on a find command cursor', {
31+
metadata: { requires: { node: '>=10.5.0' } },
32+
test: async function () {
33+
const cursor = collection.find({ bar: 1 });
2834

29-
it('should be able to use a for-await loop on a find command cursor', {
30-
metadata: { requires: { node: '>=10.5.0' } },
31-
test: async function () {
32-
const cursor = collection.find({ bar: 1 });
35+
let counter = 0;
36+
for await (const doc of cursor) {
37+
expect(doc).to.have.property('bar', 1);
38+
counter += 1;
39+
}
3340

34-
let counter = 0;
35-
for await (const doc of cursor) {
36-
expect(doc).to.have.property('bar', 1);
37-
counter += 1;
41+
expect(counter).to.equal(1000);
3842
}
43+
});
3944

40-
expect(counter).to.equal(1000);
41-
}
42-
});
45+
it('should be able to use a for-await loop on an aggregation cursor', {
46+
metadata: { requires: { node: '>=10.5.0' } },
47+
test: async function () {
48+
const cursor = collection.aggregate([{ $match: { bar: 1 } }]);
4349

44-
it('should be able to use a for-await loop on an aggregation cursor', {
45-
metadata: { requires: { node: '>=10.5.0' } },
46-
test: async function () {
47-
const cursor = collection.aggregate([{ $match: { bar: 1 } }]);
50+
let counter = 0;
51+
for await (const doc of cursor) {
52+
expect(doc).to.have.property('bar', 1);
53+
counter += 1;
54+
}
4855

49-
let counter = 0;
50-
for await (const doc of cursor) {
51-
expect(doc).to.have.property('bar', 1);
52-
counter += 1;
56+
expect(counter).to.equal(1000);
5357
}
54-
55-
expect(counter).to.equal(1000);
56-
}
57-
});
58-
59-
it('should be able to use a for-await loop on a command cursor', {
60-
metadata: { requires: { node: '>=10.5.0', mongodb: '>=3.0.0' } },
61-
test: async function () {
62-
const cursor1 = collection.listIndexes();
63-
const cursor2 = collection.listIndexes();
64-
65-
const indexes = await cursor1.toArray();
66-
let counter = 0;
67-
for await (const doc of cursor2) {
68-
expect(doc).to.exist;
69-
counter += 1;
58+
});
59+
60+
it('should be able to use a for-await loop on a command cursor', {
61+
metadata: { requires: { node: '>=10.5.0', mongodb: '>=3.0.0' } },
62+
test: async function () {
63+
const cursor1 = collection.listIndexes();
64+
const cursor2 = collection.listIndexes();
65+
66+
const indexes = await cursor1.toArray();
67+
let counter = 0;
68+
for await (const doc of cursor2) {
69+
expect(doc).to.exist;
70+
counter += 1;
71+
}
72+
73+
expect(counter).to.equal(indexes.length);
7074
}
75+
});
7176

72-
expect(counter).to.equal(indexes.length);
73-
}
74-
});
77+
it('should properly stop when cursor is closed', {
78+
metadata: { requires: { node: '>=10.5.0' } },
79+
test: async function () {
80+
const cursor = collection.find();
7581

76-
it('should properly stop when cursor is closed', {
77-
metadata: { requires: { node: '>=10.5.0' } },
78-
test: async function () {
79-
const cursor = collection.find();
82+
let count = 0;
83+
for await (const doc of cursor) {
84+
expect(doc).to.exist;
85+
count++;
86+
await cursor.close();
87+
}
8088

81-
let count = 0;
82-
for await (const doc of cursor) {
83-
expect(doc).to.exist;
84-
count++;
85-
await cursor.close();
89+
expect(count).to.equal(1);
8690
}
87-
88-
expect(count).to.equal(1);
89-
}
91+
});
92+
});
93+
context('custom promise library', () => {
94+
let client, collection, promiseSpy;
95+
before(async function () {
96+
class CustomPromise extends Promise {}
97+
promiseSpy = Sinon.spy(CustomPromise.prototype, 'then');
98+
client = this.configuration.newClient({}, { promiseLibrary: CustomPromise });
99+
100+
await client.connect();
101+
const docs = Array.from({ length: 1 }).map((_, index) => ({ foo: index, bar: 1 }));
102+
103+
collection = client.db(this.configuration.db).collection('async_cursor_tests');
104+
105+
await collection.deleteMany({});
106+
await collection.insertMany(docs);
107+
await client.close();
108+
});
109+
110+
beforeEach(async function () {
111+
client = this.configuration.newClient();
112+
await client.connect();
113+
collection = client.db(this.configuration.db).collection('async_cursor_tests');
114+
});
115+
116+
afterEach(() => {
117+
promiseSpy.restore();
118+
return client.close();
119+
});
120+
121+
it('should properly use custom promise', {
122+
metadata: { requires: { node: '>=10.5.0' } },
123+
test: async function () {
124+
const cursor = collection.find();
125+
const countBeforeIteration = promiseSpy.callCount;
126+
for await (const doc of cursor) {
127+
expect(doc).to.exist;
128+
}
129+
expect(countBeforeIteration).to.not.equal(promiseSpy.callCount);
130+
expect(promiseSpy.called).to.equal(true);
131+
}
132+
});
90133
});
91134
});

0 commit comments

Comments
 (0)