diff --git a/src/execution/execute.js b/src/execution/execute.js index f47334c6f0..6a640f3a35 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -12,6 +12,7 @@ import { GraphQLError, locatedError } from '../error'; import invariant from '../jsutils/invariant'; import isInvalid from '../jsutils/isInvalid'; import isNullish from '../jsutils/isNullish'; +import memoize3 from '../jsutils/memoize3'; import type { ObjMap } from '../jsutils/ObjMap'; import type { MaybePromise } from '../jsutils/MaybePromise'; @@ -1236,6 +1237,21 @@ function collectAndExecuteSubfields( result: mixed, ): mixed { // Collect sub-fields to execute to complete this value. + const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes); + return executeFields(exeContext, returnType, result, path, subFieldNodes); +} + +/** + * A memoized collection of relevant subfields in the context of the return + * type. Memoizing ensures the subfields are not repeatedly calculated, which + * saves overhead when resolving lists of values. + */ +const collectSubfields = memoize3(_collectSubfields); +function _collectSubfields( + exeContext: ExecutionContext, + returnType: GraphQLObjectType, + fieldNodes: $ReadOnlyArray, +): ObjMap> { let subFieldNodes = Object.create(null); const visitedFragmentNames = Object.create(null); for (let i = 0; i < fieldNodes.length; i++) { @@ -1250,8 +1266,7 @@ function collectAndExecuteSubfields( ); } } - - return executeFields(exeContext, returnType, result, path, subFieldNodes); + return subFieldNodes; } /** diff --git a/src/jsutils/memoize3.js b/src/jsutils/memoize3.js new file mode 100644 index 0000000000..95b27c7b2b --- /dev/null +++ b/src/jsutils/memoize3.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +/** + * Memoizes the provided three-argument function. + */ +export default function memoize3 any>( + fn: T, +): T { + let cache0; + function memoized(a1, a2, a3) { + if (!cache0) { + cache0 = new WeakMap(); + } + let cache1 = cache0.get(a1); + let cache2; + if (cache1) { + cache2 = cache1.get(a2); + if (cache2) { + const cachedValue = cache2.get(a3); + if (cachedValue) { + return cachedValue; + } + } + } else { + cache1 = new WeakMap(); + cache0.set(a1, cache1); + } + if (!cache2) { + cache2 = new WeakMap(); + cache1.set(a2, cache2); + } + const newValue = fn.apply(this, arguments); + cache2.set(a3, newValue); + return newValue; + } + return (memoized: any); +}