This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch TINKERPOP-3243 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 682ae7862a3457076523610a3688d6c6a7809369 Author: Stephen Mallette <[email protected]> AuthorDate: Tue Jun 16 16:47:37 2026 -0400 TINKERPOP-3243 Add next(n) batch iteration to gremlin-javascript Adds an overloaded next(amount) to Traversal that resolves to a Promise<Array> of up to `amount` result values, bringing gremlin-javascript to parity with next(n) in the Java, Python, .NET, and Go GLVs. Calling next() with no argument is unchanged and still returns the async-iterator item {value, done}. A short batch (fewer than requested) signals exhaustion and a non-positive amount yields an empty array, matching the other GLVs. Includes unit and integration test coverage and a CHANGELOG entry. Assisted-by: Claude Code:claude-opus-4-8 --- CHANGELOG.asciidoc | 1 + .../gremlin-javascript/lib/process/traversal.js | 30 +++++++++-- .../test/integration/traversal-test.js | 20 +++++++ .../gremlin-javascript/test/unit/traversal-test.js | 62 ++++++++++++++++++++++ 4 files changed, 108 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e07f7b64a1..6908e7f63e 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -26,6 +26,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima === TinkerPop 3.7.7 (Release Date: NOT OFFICIALLY RELEASED YET) * Added `NextN(n)` to `Traversal` in `gremlin-go` for batched result iteration, providing API parity with `next(n)` in the Java, Python, and .NET GLVs. +* Added `next(n)` to `Traversal` in `gremlin-javascript` for batched result iteration, providing API parity with `next(n)` in the Java, Python, and .NET GLVs. * Fixed conjoin has incorrect null handling. * Expanded `gremlin-python` CI matrix to test against Python 3.9, 3.10, 3.11, 3.12, and 3.13. * Add Node 26 support for `gremlin-javascript` and `gremlint`. diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js index a1ff9316dd..169ad63ba9 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js @@ -94,12 +94,32 @@ class Traversal { } /** - * Async iterator method implementation. - * Returns a promise containing an iterator item. - * @returns {Promise.<{value, done}>} + * Returns the next result from the traversal. + * + * When called without an argument, returns a Promise that resolves to an iterator + * item ({value, done}), following the async iterator protocol. + * + * When called with an amount, returns a Promise that resolves to an Array containing + * up to {@code amount} result values. If fewer results remain, the Array contains only + * those that remain. An amount of zero or less yields an empty Array. + * @param {Number} [amount] The number of results to retrieve. + * @returns {Promise.<{value, done}>|Promise.<Array>} */ - next() { - return this._applyStrategies().then(() => this._getNext()); + next(amount) { + if (amount === undefined || amount === null) { + return this._applyStrategies().then(() => this._getNext()); + } + return this._applyStrategies().then(() => { + const result = []; + for (let i = 0; i < amount; i++) { + const it = this._getNext(); + if (it.done) { + break; + } + result.push(it.value); + } + return result; + }); } /** diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js index ede1ee8e52..31bb36425d 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js @@ -129,6 +129,26 @@ describe('Traversal', function () { }); }); }); + describe('#next(amount)', function () { + it('should submit the traversal and return a batch of up to amount results', function () { + var g = traversal().withRemote(connection); + var t = g.V(); + return t.next(2) + .then(function (batch) { + assert.ok(Array.isArray(batch)); + assert.strictEqual(batch.length, 2); + batch.forEach(v => assert.ok(v instanceof Vertex)); + return t.next(10); + }).then(function (batch) { + // gmodern has 6 vertices, 2 already consumed, so only 4 remain + assert.strictEqual(batch.length, 4); + batch.forEach(v => assert.ok(v instanceof Vertex)); + return t.next(2); + }).then(function (batch) { + assert.deepStrictEqual(batch, []); + }); + }); + }); describe('#materializeProperties()', function () { it('should skip vertex properties when tokens is set', function () { var g = traversal().withRemote(connection); diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js index b794112f38..37a7920ca2 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js @@ -157,6 +157,68 @@ describe('Traversal', function () { }); }); + describe('#next(amount)', function () { + function createTraversal(traversers) { + const strategyMock = { + apply: function (traversal) { + traversal.traversers = traversers; + return Promise.resolve(); + } + }; + const strategies = new TraversalStrategies(); + strategies.addStrategy(strategyMock); + return new t.Traversal(null, strategies, null); + } + + it('should return a Promise with an array of up to amount values', function () { + const traversal = createTraversal([ new t.Traverser(1, 1), new t.Traverser(2, 1), new t.Traverser(3, 1) ]); + return traversal.next(2) + .then(function (batch) { + assert.deepStrictEqual(batch, [ 1, 2 ]); + return traversal.next(2); + }) + .then(function (batch) { + assert.deepStrictEqual(batch, [ 3 ]); + }); + }); + + it('should expand bulk into separate values', function () { + const traversal = createTraversal([ new t.Traverser(1, 2), new t.Traverser(2, 1) ]); + return traversal.next(2).then(function (batch) { + assert.deepStrictEqual(batch, [ 1, 1 ]); + }); + }); + + it('should return only the remaining values when fewer than amount exist', function () { + const traversal = createTraversal([ new t.Traverser(1, 1), new t.Traverser(2, 1) ]); + return traversal.next(5).then(function (batch) { + assert.deepStrictEqual(batch, [ 1, 2 ]); + }); + }); + + it('should return an empty array when amount is zero', function () { + const traversal = createTraversal([ new t.Traverser(1, 1) ]); + return traversal.next(0).then(function (batch) { + assert.deepStrictEqual(batch, []); + }); + }); + + it('should return an empty array when amount is negative', function () { + const traversal = createTraversal([ new t.Traverser(1, 1) ]); + return traversal.next(-1).then(function (batch) { + assert.deepStrictEqual(batch, []); + }); + }); + + it('should still return an iterator item when called without an amount', function () { + const traversal = createTraversal([ new t.Traverser(1, 1) ]); + return traversal.next().then(function (item) { + assert.strictEqual(item.value, 1); + assert.strictEqual(item.done, false); + }); + }); + }); + if (Symbol.asyncIterator) { describe('@@asyncIterator', function () { it('should expose the async iterator', function () {
