From 5b5f33a8248ff62c6de0bdca78656e2158ef34a0 Mon Sep 17 00:00:00 2001 From: Christopher Anderson Date: Tue, 31 Jul 2018 12:24:53 -0700 Subject: [PATCH] Update forEach/Adds AsyncIterator (#87) Switches the forEach implementation to be AsyncIterator instead. Additionally, it changes the forEach implementation to accept a callback fixes https://github.com/Azure/azure-cosmos-js/issues/73 fixes https://github.com/Azure/azure-cosmos-js/issues/71 --- src/queryIterator.ts | 61 ++++++++++++++++++++- src/test/functional/query.spec.ts | 26 ++++++++- src/test/integration/aggregateQuery.spec.ts | 2 +- src/test/integration/crossPartition.spec.ts | 2 +- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/queryIterator.ts b/src/queryIterator.ts index e92e4cd..8c75c2e 100644 --- a/src/queryIterator.ts +++ b/src/queryIterator.ts @@ -35,10 +35,67 @@ export class QueryIterator { this.resourceLink = resourceLink; this.queryExecutionContext = this._createQueryExecutionContext(); } + /** - * Execute a provided function once per feed element. + * Calls a specified callback for each item returned from the query. + * Runs serially; each callback blocks the next. + * + * @param callback Specified callback. + * First param is the result, + * second param (optional) is the current headers object state, + * third param (optional) is current index. + * No more callbacks will be called if one of them results false. + * + * @returns Promise - you should await or .catch the Promise in case there are any errors + * + * @example Iterate over all databases + * ```typescript + * await client.databases.readAll().forEach((db, headers, index) => { + * console.log(`Got ${db.id} from forEach`); + * }) + * ``` */ - public async *forEach(): AsyncIterable> { + public async forEach(callback: (result: T, headers?: IHeaders, index?: number) => boolean | void): Promise { + this.reset(); + let index = 0; + while (this.queryExecutionContext.hasMoreResults()) { + const result = await this.queryExecutionContext.nextItem(); + if (result.result === undefined) { + return; + } + if (callback(result.result, result.headers, index) === false) { + return; + } else { + ++index; + } + } + } + + /** + * Gets an async iterator that will yield results until completion. + * + * NOTE: AsyncIterators are a very new feature and you might need to + * use polyfils/etc. in order to use them in your code. + * + * If you're using TypeScript, you can use the following polyfill as long + * as you target ES6 or higher and are running on Node 6 or higher. + * + * ```typescript + * if (!Symbol || !Symbol.asyncIterator) { + * (Symbol as any).asyncIterator = Symbol.for("Symbol.asyncIterator"); + * } + * ``` + * + * @see QueryIterator.forEach for very similar functionality. + * + * @example Iterate over all databases + * ```typescript + * for await(const {result: db} in client.databases.readAll().getAsyncIterator()) { + * console.log(`Got ${db.id} from AsyncIterator`); + * } + * ``` + */ + public async *getAsyncIterator(): AsyncIterable> { this.reset(); while (this.queryExecutionContext.hasMoreResults()) { const result = await this.queryExecutionContext.nextItem(); diff --git a/src/test/functional/query.spec.ts b/src/test/functional/query.spec.ts index b73d633..0bd36fd 100644 --- a/src/test/functional/query.spec.ts +++ b/src/test/functional/query.spec.ts @@ -117,10 +117,10 @@ describe("NodeJS CRUD Tests", function() { assert.equal(docs[2].id, resources.doc3.id); }; - const queryIteratorForEachTest = async function() { + const queryIteratorAsyncIteratorTest = async function() { const queryIterator = resources.container.items.readAll({ maxItemCount: 2 }); let counter = 0; - for await (const { result: doc } of queryIterator.forEach()) { + for await (const { result: doc } of queryIterator.getAsyncIterator()) { counter++; if (counter === 1) { assert.equal(doc.id, resources.doc1.id, "first document should be doc1"); @@ -133,6 +133,22 @@ describe("NodeJS CRUD Tests", function() { assert(counter === 3, "iterator should have run 3 times"); }; + const queryIteratorForEachTest = async function() { + const queryIterator = resources.container.items.readAll({ maxItemCount: 2 }); + let counter = 0; + await queryIterator.forEach((item, headers, index) => { + counter++; + if (index === 0) { + assert.equal(item.id, resources.doc1.id, "first document should be doc1"); + } else if (index === 1) { + assert.equal(item.id, resources.doc2.id, "second document should be doc2"); + } else if (index === 2) { + assert.equal(item.id, resources.doc3.id, "third document should be doc3"); + } + }); + assert(counter === 3, "iterator should have run 3 times"); + }; + const queryIteratorNextAndMoreTest = async function() { const queryIterator = resources.container.items.readAll({ maxItemCount: 2 }); assert.equal(queryIterator.hasMoreResults(), true); @@ -186,7 +202,11 @@ describe("NodeJS CRUD Tests", function() { await queryIteratorToArrayTest(); }); - it("nativeApi validate queryIterator iterator forEach name based", async function() { + it("validate queryIterator asyncIterator", async function() { + await queryIteratorAsyncIteratorTest(); + }); + + it("validate queryIterator forEach", async function() { await queryIteratorForEachTest(); }); diff --git a/src/test/integration/aggregateQuery.spec.ts b/src/test/integration/aggregateQuery.spec.ts index 3c745f6..0a187e4 100644 --- a/src/test/integration/aggregateQuery.spec.ts +++ b/src/test/integration/aggregateQuery.spec.ts @@ -183,7 +183,7 @@ describe.skip("NodeJS Aggregate Query Tests", async function() { const results: any[] = []; let callbackSingnalledEnd = false; // forEach uses callbacks still, so just wrap in a promise - for await (const { result: item } of queryIterator.forEach()) { + for await (const { result: item } of queryIterator.getAsyncIterator()) { // if the previous invocation returned false, forEach must avoid invoking the callback again! assert.equal(callbackSingnalledEnd, false, "forEach called callback after the first false returned"); results.push(item); diff --git a/src/test/integration/crossPartition.spec.ts b/src/test/integration/crossPartition.spec.ts index 202397e..b58ef55 100644 --- a/src/test/integration/crossPartition.spec.ts +++ b/src/test/integration/crossPartition.spec.ts @@ -231,7 +231,7 @@ describe("Cross Partition", function() { const results: any[] = []; let callbackSingnalledEnd = false; // forEach uses callbacks still, so just wrap in a promise - for await (const { result: item } of queryIterator.forEach()) { + for await (const { result: item } of queryIterator.getAsyncIterator()) { // if the previous invocation returned false, forEach must avoid invoking the callback again! assert.equal(callbackSingnalledEnd, false, "forEach called callback after the first false returned"); results.push(item);