From f3e2954126e0005f7b01b2954071d4a91fd4f2b2 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Mon, 31 Aug 2020 16:48:30 +0300 Subject: [PATCH] Move 'isAsyncIterable' into 'jsutils' (#2775) --- src/jsutils/__tests__/isAsyncIterable-test.js | 53 +++++++++++++++++++ src/jsutils/isAsyncIterable.js | 17 ++++++ src/subscription/subscribe.js | 21 ++------ 3 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 src/jsutils/__tests__/isAsyncIterable-test.js create mode 100644 src/jsutils/isAsyncIterable.js diff --git a/src/jsutils/__tests__/isAsyncIterable-test.js b/src/jsutils/__tests__/isAsyncIterable-test.js new file mode 100644 index 0000000000..623d429265 --- /dev/null +++ b/src/jsutils/__tests__/isAsyncIterable-test.js @@ -0,0 +1,53 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import identityFunc from '../identityFunc'; +import isAsyncIterable from '../isAsyncIterable'; + +describe('isAsyncIterable', () => { + it('should return `true` for AsyncIterable', () => { + // $FlowFixMe[prop-missing] Flow doesn't support Symbol.asyncIterator + const asyncIteratable = { [Symbol.asyncIterator]: identityFunc }; + expect(isAsyncIterable(asyncIteratable)).to.equal(true); + + // istanbul ignore next (Never called and use just as a placeholder) + async function* asyncGeneratorFunc() { + /* do nothing */ + } + + expect(isAsyncIterable(asyncGeneratorFunc())).to.equal(true); + + // But async generator function itself is not iteratable + expect(isAsyncIterable(asyncGeneratorFunc)).to.equal(false); + }); + + it('should return `false` for all other values', () => { + expect(isAsyncIterable(null)).to.equal(false); + expect(isAsyncIterable(undefined)).to.equal(false); + + expect(isAsyncIterable('ABC')).to.equal(false); + expect(isAsyncIterable('0')).to.equal(false); + expect(isAsyncIterable('')).to.equal(false); + + expect(isAsyncIterable([])).to.equal(false); + expect(isAsyncIterable(new Int8Array(1))).to.equal(false); + + expect(isAsyncIterable({})).to.equal(false); + expect(isAsyncIterable({ iterable: true })).to.equal(false); + + const iterator = { [Symbol.iterator]: identityFunc }; + expect(isAsyncIterable(iterator)).to.equal(false); + + // istanbul ignore next (Never called and use just as a placeholder) + function* generatorFunc() { + /* do nothing */ + } + expect(isAsyncIterable(generatorFunc())).to.equal(false); + + const invalidAsyncIteratable = { + // $FlowFixMe[prop-missing] Flow doesn't support Symbol.asyncIterator + [Symbol.asyncIterator]: { next: identityFunc }, + }; + expect(isAsyncIterable(invalidAsyncIteratable)).to.equal(false); + }); +}); diff --git a/src/jsutils/isAsyncIterable.js b/src/jsutils/isAsyncIterable.js new file mode 100644 index 0000000000..3dbbf9e214 --- /dev/null +++ b/src/jsutils/isAsyncIterable.js @@ -0,0 +1,17 @@ +import { SYMBOL_ASYNC_ITERATOR } from '../polyfills/symbols'; + +/** + * Returns true if the provided object implements the AsyncIterator protocol via + * either implementing a `Symbol.asyncIterator` or `"@@asyncIterator"` method. + */ +declare function isAsyncIterable(value: mixed): boolean %checks(value instanceof + AsyncIterable); + +// eslint-disable-next-line no-redeclare +export default function isAsyncIterable(maybeAsyncIterable) { + if (maybeAsyncIterable == null || typeof maybeAsyncIterable !== 'object') { + return false; + } + + return typeof maybeAsyncIterable[SYMBOL_ASYNC_ITERATOR] === 'function'; +} diff --git a/src/subscription/subscribe.js b/src/subscription/subscribe.js index a0c4ea9090..9a46de1968 100644 --- a/src/subscription/subscribe.js +++ b/src/subscription/subscribe.js @@ -1,6 +1,5 @@ -import { SYMBOL_ASYNC_ITERATOR } from '../polyfills/symbols'; - import inspect from '../jsutils/inspect'; +import isAsyncIterable from '../jsutils/isAsyncIterable'; import { addPath, pathToArray } from '../jsutils/Path'; import { GraphQLError } from '../error/GraphQLError'; @@ -158,7 +157,7 @@ function subscribeImpl( // Note: Flow can't refine isAsyncIterable, so explicit casts are used. isAsyncIterable(resultOrStream) ? mapAsyncIterator( - ((resultOrStream: any): AsyncIterable), + resultOrStream, mapSourceToResponse, reportGraphQLError, ) @@ -289,24 +288,10 @@ function executeSubscription( `Received: ${inspect(eventStream)}.`, ); } - - // Note: isAsyncIterable above ensures this will be correct. - return ((eventStream: any): AsyncIterable); + return eventStream; }, (error) => { throw locatedError(error, fieldNodes, pathToArray(path)); }, ); } - -/** - * Returns true if the provided object implements the AsyncIterator protocol via - * either implementing a `Symbol.asyncIterator` or `"@@asyncIterator"` method. - */ -function isAsyncIterable(maybeAsyncIterable: mixed): boolean { - if (maybeAsyncIterable == null || typeof maybeAsyncIterable !== 'object') { - return false; - } - - return typeof maybeAsyncIterable[SYMBOL_ASYNC_ITERATOR] === 'function'; -}