Repository: graphql/graphql-js
Pull request #3696 (2022-08-14 21:07:10)

polish: mapAsyncIterator
1. rename to mapAsyncIterable => to better reflect function argument
2. use `expectPromise` in tests

extracted from #3694

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *compassionate-pike-271cb3* processing.


|  Name | Link |
|---------------------------------|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | d99b6de138b3cbefd7f819cb402afdbe1c7951ac |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/62f96400b6c0a30008da3aa3 |
github-actions[bot]: Hi @yaacovCR, I'm @github-actions bot happy to help you with this PR 👋

<details>
<summary> Supported commands </summary>

Please post this commands in separate comments and only one per comment:
* `@github-actions run-benchmark` - Run benchmark comparing base and merge commits for this PR
* `@github-actions publish-pr-on-npm` - Build package from this PR and publish it on NPM
</details>

IvanGoncharov: @yaacovCR Please preserve commit during merging:
<img width="500" alt="image" src="https://user-images.githubusercontent.com/8336157/184622888-f2e3dc30-1c69-42c5-822e-8bc6d6845542.png">


Patch:
diff --git a/resources/benchmark.ts b/resources/benchmark.ts
index 0564f2a2..5f79776f 100644
--- a/resources/benchmark.ts
+++ b/resources/benchmark.ts
@@ -5,7 +5,7 @@ import * as os from 'node:os';
 import * as path from 'node:path';
 import * as url from 'node:url';
 
-import { localRepoPath } from './utils';
+import { exec, execOutput, localRepoPath } from './utils';
 
 const NS_PER_SEC = 1e9;
 const LOCAL = 'local';
@@ -25,15 +25,6 @@ function runBenchmarks() {
   }
 }
 
-function exec(command: string, options = {}) {
-  const result = cp.execSync(command, {
-    encoding: 'utf-8',
-    stdio: ['inherit', 'pipe', 'inherit'],
-    ...options,
-  });
-  return result?.trimEnd();
-}
-
 interface BenchmarkProject {
   revision: string;
   projectPath: string;
@@ -86,7 +77,7 @@ function prepareBenchmarkProjects(
     }
 
     // Returns the complete git hash for a given git revision reference.
-    const hash = exec(`git rev-parse "${revision}"`);
+    const hash = execOutput(`git rev-parse "${revision}"`);
 
     const archivePath = path.join(tmpDir, `graphql-${hash}.tgz`);
     if (fs.existsSync(archivePath)) {
@@ -108,7 +99,9 @@ function prepareBenchmarkProjects(
     exec('npm --quiet run build:npm', { cwd: repoDir });
 
     const distDir = path.join(repoDir, 'npmDist');
-    const archiveName = exec(`npm --quiet pack ${distDir}`, { cwd: repoDir });
+    const archiveName = execOutput(`npm --quiet pack ${distDir}`, {
+      cwd: repoDir,
+    });
     return path.join(repoDir, archiveName);
   }
 }
diff --git a/src/execution/execute.ts b/src/execution/execute.ts
index e7b7e4e1..c288e5e5 100644
--- a/src/execution/execute.ts
+++ b/src/execution/execute.ts
@@ -51,7 +51,7 @@ import {
   collectFields,
   collectSubfields as _collectSubfields,
 } from './collectFields';
-import { mapAsyncIterator } from './mapAsyncIterator';
+import { mapAsyncIterable } from './mapAsyncIterable';
 import { getArgumentValues, getVariableValues } from './values';
 
 /* eslint-disable max-params */
@@ -1143,7 +1143,7 @@ function mapSourceToResponse(
   // the GraphQL specification. The `execute` function provides the
   // "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
   // "ExecuteQuery" algorithm, for which `execute` is also used.
-  return mapAsyncIterator(resultOrStream, (payload: unknown) =>
+  return mapAsyncIterable(resultOrStream, (payload: unknown) =>
     executeImpl(buildPerEventExecutionContext(exeContext, payload)),
   );
 }
diff --git a/src/execution/mapAsyncIterator.ts b/src/execution/mapAsyncIterable.ts
similarity index 96%
rename from src/execution/mapAsyncIterator.ts
rename to src/execution/mapAsyncIterable.ts
index 82e863c6..f61855c8 100644
--- a/src/execution/mapAsyncIterator.ts
+++ b/src/execution/mapAsyncIterable.ts
@@ -4,7 +4,7 @@ import type { PromiseOrValue } from '../jsutils/PromiseOrValue';
  * Given an AsyncIterable and a callback function, return an AsyncIterator
  * which produces values mapped via calling the callback function.
  */
-export function mapAsyncIterator<T, U, R = undefined>(
+export function mapAsyncIterable<T, U, R = undefined>(
   iterable: AsyncGenerator<T, R, void> | AsyncIterable<T>,
   callback: (value: T) => PromiseOrValue<U>,
 ): AsyncGenerator<U, R, void> {


Test patch:
diff --git a/src/execution/__tests__/mapAsyncIterator-test.ts b/src/execution/__tests__/mapAsyncIterable-test.ts
similarity index 82%
rename from src/execution/__tests__/mapAsyncIterator-test.ts
rename to src/execution/__tests__/mapAsyncIterable-test.ts
index 81070757..d70714fa 100644
--- a/src/execution/__tests__/mapAsyncIterator-test.ts
+++ b/src/execution/__tests__/mapAsyncIterable-test.ts
@@ -1,10 +1,12 @@
 import { expect } from 'chai';
 import { describe, it } from 'mocha';
 
-import { mapAsyncIterator } from '../mapAsyncIterator';
+import { expectPromise } from '../../__testUtils__/expectPromise';
+
+import { mapAsyncIterable } from '../mapAsyncIterable';
 
 /* eslint-disable @typescript-eslint/require-await */
-describe('mapAsyncIterator', () => {
+describe('mapAsyncIterable', () => {
   it('maps over async generator', async () => {
     async function* source() {
       yield 1;
@@ -12,7 +14,7 @@ describe('mapAsyncIterator', () => {
       yield 3;
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -42,7 +44,7 @@ describe('mapAsyncIterator', () => {
       },
     };
 
-    const doubles = mapAsyncIterator(iterable, (x) => x + x);
+    const doubles = mapAsyncIterable(iterable, (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -60,7 +62,7 @@ describe('mapAsyncIterator', () => {
       yield 3;
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     const result = [];
     for await (const x of doubles) {
@@ -76,7 +78,7 @@ describe('mapAsyncIterator', () => {
       yield 3;
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => Promise.resolve(x + x));
+    const doubles = mapAsyncIterable(source(), (x) => Promise.resolve(x + x));
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -100,7 +102,7 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -139,7 +141,7 @@ describe('mapAsyncIterator', () => {
       },
     };
 
-    const doubles = mapAsyncIterator(iterable, (x) => x + x);
+    const doubles = mapAsyncIterable(iterable, (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -164,7 +166,7 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 'aa', done: false });
     expect(await doubles.next()).to.deep.equal({ value: 'bb', done: false });
@@ -203,20 +205,15 @@ describe('mapAsyncIterator', () => {
       },
     };
 
-    const doubles = mapAsyncIterator(iterable, (x) => x + x);
+    const doubles = mapAsyncIterable(iterable, (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
 
     // Throw error
-    let caughtError;
-    try {
-      /* c8 ignore next 2 */
-      await doubles.throw('ouch');
-    } catch (e) {
-      caughtError = e;
-    }
-    expect(caughtError).to.equal('ouch');
+    const message = 'allows throwing errors when mapping async iterable';
+    const thrown = doubles.throw(new Error(message));
+    await expectPromise(thrown).toRejectWith(message);
   });
 
   it('passes through caught errors through async generators', async () => {
@@ -231,7 +228,7 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -258,24 +255,14 @@ describe('mapAsyncIterator', () => {
       throw new Error('Goodbye');
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({
       value: 'HelloHello',
       done: false,
     });
 
-    let caughtError;
-    try {
-      /* c8 ignore next 2 */
-      await doubles.next();
-    } catch (e) {
-      caughtError = e;
-    }
-
-    expect(caughtError)
-      .to.be.an.instanceOf(Error)
-      .with.property('message', 'Goodbye');
+    await expectPromise(doubles.next()).toRejectWith('Goodbye');
   });
 
   async function testClosesSourceWithMapper<T>(mapper: (value: number) => T) {
@@ -293,21 +280,11 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const throwOver1 = mapAsyncIterator(source(), mapper);
+    const throwOver1 = mapAsyncIterable(source(), mapper);
 
     expect(await throwOver1.next()).to.deep.equal({ value: 1, done: false });
 
-    let expectedError;
-    try {
-      /* c8 ignore next 2 */
-      await throwOver1.next();
-    } catch (error) {
-      expectedError = error;
-    }
-
-    expect(expectedError)
-      .to.be.an.instanceOf(Error)
-      .with.property('message', 'Cannot count to 2');
+    await expectPromise(throwOver1.next()).toRejectWith('Cannot count to 2');
 
     expect(await throwOver1.next()).to.deep.equal({
       value: undefined,