Skip to content

Commit 15e8225

Browse files
refactor: make cursor internal next function async-await
1 parent 57814d6 commit 15e8225

File tree

1 file changed

+48
-49
lines changed

1 file changed

+48
-49
lines changed

src/cursor/abstract_cursor.ts

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Readable, Transform } from 'stream';
2-
import { promisify } from 'util';
2+
import { callbackify, promisify } from 'util';
33

44
import { type BSONSerializeOptions, type Document, Long, pluckBSONSerializeOptions } from '../bson';
55
import {
@@ -361,7 +361,7 @@ export abstract class AbstractCursor<
361361
return true;
362362
}
363363

364-
const doc = await nextAsync<TSchema>(this, true);
364+
const doc = await next<TSchema>(this, true);
365365

366366
if (doc) {
367367
this[kDocuments].unshift(doc);
@@ -377,7 +377,7 @@ export abstract class AbstractCursor<
377377
throw new MongoCursorExhaustedError();
378378
}
379379

380-
return nextAsync(this, true);
380+
return next(this, true);
381381
}
382382

383383
/**
@@ -388,7 +388,7 @@ export abstract class AbstractCursor<
388388
throw new MongoCursorExhaustedError();
389389
}
390390

391-
return nextAsync(this, false);
391+
return next(this, false);
392392
}
393393

394394
/**
@@ -690,78 +690,77 @@ function nextDocument<T>(cursor: AbstractCursor<T>): T | null {
690690
return doc;
691691
}
692692

693-
const nextAsync = promisify(
694-
next as <T>(
695-
cursor: AbstractCursor<T>,
696-
blocking: boolean,
697-
callback: (e: Error, r: T | null) => void
698-
) => void
699-
);
700-
701693
/**
702694
* @param cursor - the cursor on which to call `next`
703695
* @param blocking - a boolean indicating whether or not the cursor should `block` until data
704696
* is available. Generally, this flag is set to `false` because if the getMore returns no documents,
705697
* the cursor has been exhausted. In certain scenarios (ChangeStreams, tailable await cursors and
706698
* `tryNext`, for example) blocking is necessary because a getMore returning no documents does
707699
* not indicate the end of the cursor.
708-
* @param callback - callback to return the result to the caller
709-
* @returns
700+
* @returns the next document in the cursor, or `null`. When `blocking` is `true`, a `null` document means
701+
* the cursor has been exhausted. Otherwise, it means that there is no document available in the cursor's buffer.
710702
*/
711-
export function next<T>(
712-
cursor: AbstractCursor<T>,
713-
blocking: boolean,
714-
callback: Callback<T | null>
715-
): void {
703+
async function next<T>(cursor: AbstractCursor<T>, blocking: boolean): Promise<T | null> {
716704
const cursorId = cursor[kId];
717705
if (cursor.closed) {
718-
return callback(undefined, null);
706+
return null;
719707
}
720708

721709
if (cursor[kDocuments].length !== 0) {
722-
callback(undefined, nextDocument<T>(cursor));
723-
return;
710+
return nextDocument<T>(cursor);
724711
}
725712

726713
if (cursorId == null) {
727714
// All cursors must operate within a session, one must be made implicitly if not explicitly provided
728-
cursor[kInit](err => {
729-
if (err) return callback(err);
730-
return next(cursor, blocking, callback);
731-
});
732-
733-
return;
715+
const init = promisify(cb => cursor[kInit](cb));
716+
await init();
717+
return next(cursor, blocking);
734718
}
735719

736720
if (cursorIsDead(cursor)) {
737-
return cleanupCursor(cursor, undefined, () => callback(undefined, null));
721+
try {
722+
await cleanupCursorAsync(cursor, undefined);
723+
// eslint-disable-next-line no-empty
724+
} catch {}
725+
return null;
738726
}
739727

740728
// otherwise need to call getMore
741729
const batchSize = cursor[kOptions].batchSize || 1000;
742-
cursor._getMore(batchSize, (error, response) => {
743-
if (response) {
744-
const cursorId =
745-
typeof response.cursor.id === 'number'
746-
? Long.fromNumber(response.cursor.id)
747-
: typeof response.cursor.id === 'bigint'
748-
? Long.fromBigInt(response.cursor.id)
749-
: response.cursor.id;
750-
751-
cursor[kDocuments].pushMany(response.cursor.nextBatch);
752-
cursor[kId] = cursorId;
753-
}
754-
730+
const getMore = promisify((batchSize: number, cb: Callback<Document | undefined>) =>
731+
cursor._getMore(batchSize, cb)
732+
);
733+
734+
let response: Document | undefined;
735+
try {
736+
response = await getMore(batchSize);
737+
} catch (error) {
755738
if (error || cursorIsDead(cursor)) {
756-
return cleanupCursor(cursor, { error }, () => callback(error, nextDocument<T>(cursor)));
739+
try {
740+
await cleanupCursorAsync(cursor, { error });
741+
// eslint-disable-next-line no-empty
742+
} catch {}
743+
throw error;
757744
}
745+
}
758746

759-
if (cursor[kDocuments].length === 0 && blocking === false) {
760-
return callback(undefined, null);
761-
}
747+
if (response) {
748+
const cursorId =
749+
typeof response.cursor.id === 'number'
750+
? Long.fromNumber(response.cursor.id)
751+
: typeof response.cursor.id === 'bigint'
752+
? Long.fromBigInt(response.cursor.id)
753+
: response.cursor.id;
754+
755+
cursor[kDocuments].pushMany(response.cursor.nextBatch);
756+
cursor[kId] = cursorId;
757+
}
758+
759+
if (cursor[kDocuments].length === 0 && blocking === false) {
760+
return null;
761+
}
762762

763-
next(cursor, blocking, callback);
764-
});
763+
return next(cursor, blocking);
765764
}
766765

767766
function cursorIsDead(cursor: AbstractCursor): boolean {
@@ -881,7 +880,7 @@ class ReadableCursorStream extends Readable {
881880
}
882881

883882
private _readNext() {
884-
next(this._cursor, true, (err, result) => {
883+
callbackify(next)(this._cursor, true, (err, result) => {
885884
if (err) {
886885
// NOTE: This is questionable, but we have a test backing the behavior. It seems the
887886
// desired behavior is that a stream ends cleanly when a user explicitly closes

0 commit comments

Comments
 (0)