Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 32 additions & 42 deletions src/execution/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

import { forEach, isCollection } from 'iterall';
import { GraphQLError, locatedError } from '../error';
import getPromise from '../jsutils/getPromise';
import invariant from '../jsutils/invariant';
import isInvalid from '../jsutils/isInvalid';
import isNullish from '../jsutils/isNullish';
import isPromise from '../jsutils/isPromise';
import memoize3 from '../jsutils/memoize3';
import promiseForObject from '../jsutils/promiseForObject';
import promiseReduce from '../jsutils/promiseReduce';
Expand Down Expand Up @@ -229,9 +229,8 @@ function buildResponse(
context: ExecutionContext,
data: MaybePromise<ObjMap<mixed> | null>,
) {
const promise = getPromise(data);
if (promise) {
return promise.then(resolved => buildResponse(context, resolved));
if (isPromise(data)) {
return data.then(resolved => buildResponse(context, resolved));
}
return context.errors.length === 0
? { data }
Expand Down Expand Up @@ -403,9 +402,8 @@ function executeOperation(
operation.operation === 'mutation'
? executeFieldsSerially(exeContext, type, rootValue, path, fields)
: executeFields(exeContext, type, rootValue, path, fields);
const promise = getPromise(result);
if (promise) {
return promise.then(undefined, error => {
if (isPromise(result)) {
return result.then(undefined, error => {
exeContext.errors.push(error);
return Promise.resolve(null);
});
Expand Down Expand Up @@ -484,9 +482,8 @@ function executeFieldsSerially(
if (result === undefined) {
return results;
}
const promise = getPromise(result);
if (promise) {
return promise.then(resolvedResult => {
if (isPromise(result)) {
return result.then(resolvedResult => {
results[responseName] = resolvedResult;
return results;
});
Expand Down Expand Up @@ -525,7 +522,7 @@ function executeFields(
return results;
}
results[responseName] = result;
if (getPromise(result)) {
if (!containsPromise && isPromise(result)) {
containsPromise = true;
}
return results;
Expand Down Expand Up @@ -684,7 +681,7 @@ function resolveField(
source: mixed,
fieldNodes: $ReadOnlyArray<FieldNode>,
path: ResponsePath,
): mixed {
): MaybePromise<mixed> {
const fieldNode = fieldNodes[0];
const fieldName = fieldNode.name.value;

Expand Down Expand Up @@ -773,8 +770,7 @@ export function resolveFieldValueOrError<TSource>(
const context = exeContext.contextValue;

const result = resolveFn(source, args, context, info);
const promise = getPromise(result);
return promise ? promise.then(undefined, asErrorInstance) : result;
return isPromise(result) ? result.then(undefined, asErrorInstance) : result;
} catch (error) {
return asErrorInstance(error);
}
Expand All @@ -795,7 +791,7 @@ function completeValueCatchingError(
info: GraphQLResolveInfo,
path: ResponsePath,
result: mixed,
): mixed {
): MaybePromise<mixed> {
// If the field type is non-nullable, then it is resolved without any
// protection from errors, however it still properly locates the error.
if (isNonNullType(returnType)) {
Expand All @@ -820,13 +816,12 @@ function completeValueCatchingError(
path,
result,
);
const promise = getPromise(completed);
if (promise) {
if (isPromise(completed)) {
// If `completeValueWithLocatedError` returned a rejected promise, log
// the rejection error and resolve to null.
// Note: we don't rely on a `catch` method, but we do expect "thenable"
// to take a second callback for the error case.
return promise.then(undefined, error => {
return completed.then(undefined, error => {
exeContext.errors.push(error);
return Promise.resolve(null);
});
Expand All @@ -849,7 +844,7 @@ function completeValueWithLocatedError(
info: GraphQLResolveInfo,
path: ResponsePath,
result: mixed,
): mixed {
): MaybePromise<mixed> {
try {
const completed = completeValue(
exeContext,
Expand All @@ -859,9 +854,8 @@ function completeValueWithLocatedError(
path,
result,
);
const promise = getPromise(completed);
if (promise) {
return promise.then(undefined, error =>
if (isPromise(completed)) {
return completed.then(undefined, error =>
Promise.reject(
locatedError(
asErrorInstance(error),
Expand Down Expand Up @@ -909,11 +903,10 @@ function completeValue(
info: GraphQLResolveInfo,
path: ResponsePath,
result: mixed,
): mixed {
): MaybePromise<mixed> {
// If result is a Promise, apply-lift over completeValue.
const promise = getPromise(result);
if (promise) {
return promise.then(resolved =>
if (isPromise(result)) {
return result.then(resolved =>
completeValue(exeContext, returnType, fieldNodes, info, path, resolved),
);
}
Expand Down Expand Up @@ -1012,7 +1005,7 @@ function completeListValue(
info: GraphQLResolveInfo,
path: ResponsePath,
result: mixed,
): mixed {
): MaybePromise<$ReadOnlyArray<mixed>> {
invariant(
isCollection(result),
`Expected Iterable, but did not find one for field ${
Expand All @@ -1038,7 +1031,7 @@ function completeListValue(
item,
);

if (!containsPromise && getPromise(completedItem)) {
if (!containsPromise && isPromise(completedItem)) {
containsPromise = true;
}
completedResults.push(completedItem);
Expand Down Expand Up @@ -1074,14 +1067,13 @@ function completeAbstractValue(
info: GraphQLResolveInfo,
path: ResponsePath,
result: mixed,
): mixed {
): MaybePromise<ObjMap<mixed>> {
const runtimeType = returnType.resolveType
? returnType.resolveType(result, exeContext.contextValue, info)
: defaultResolveTypeFn(result, exeContext.contextValue, info, returnType);

const promise = getPromise(runtimeType);
if (promise) {
return promise.then(resolvedRuntimeType =>
if (isPromise(runtimeType)) {
return runtimeType.then(resolvedRuntimeType =>
completeObjectValue(
exeContext,
ensureValidRuntimeType(
Expand All @@ -1103,7 +1095,7 @@ function completeAbstractValue(
return completeObjectValue(
exeContext,
ensureValidRuntimeType(
((runtimeType: any): ?GraphQLObjectType | string),
runtimeType,
exeContext,
returnType,
fieldNodes,
Expand Down Expand Up @@ -1163,17 +1155,16 @@ function completeObjectValue(
info: GraphQLResolveInfo,
path: ResponsePath,
result: mixed,
): mixed {
): MaybePromise<ObjMap<mixed>> {
// If there is an isTypeOf predicate function, call it with the
// current result. If isTypeOf returns false, then raise an error rather
// than continuing execution.
if (returnType.isTypeOf) {
const isTypeOf = returnType.isTypeOf(result, exeContext.contextValue, info);

const promise = getPromise(isTypeOf);
if (promise) {
return promise.then(isTypeOfResult => {
if (!isTypeOfResult) {
if (isPromise(isTypeOf)) {
return isTypeOf.then(resolvedIsTypeOf => {
if (!resolvedIsTypeOf) {
throw invalidReturnTypeError(returnType, result, fieldNodes);
}
return collectAndExecuteSubfields(
Expand Down Expand Up @@ -1220,7 +1211,7 @@ function collectAndExecuteSubfields(
info: GraphQLResolveInfo,
path: ResponsePath,
result: mixed,
): mixed {
): MaybePromise<ObjMap<mixed>> {
// Collect sub-fields to execute to complete this value.
const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes);
return executeFields(exeContext, returnType, result, path, subFieldNodes);
Expand Down Expand Up @@ -1289,9 +1280,8 @@ function defaultResolveTypeFn(
if (type.isTypeOf) {
const isTypeOfResult = type.isTypeOf(value, context, info);

const promise = getPromise(isTypeOfResult);
if (promise) {
promisedIsTypeOfResults[i] = promise;
if (isPromise(isTypeOfResult)) {
promisedIsTypeOfResults[i] = isTypeOfResult;
} else if (isTypeOfResult) {
return type;
}
Expand Down
24 changes: 0 additions & 24 deletions src/jsutils/getPromise.js

This file was deleted.

20 changes: 20 additions & 0 deletions src/jsutils/isPromise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-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 strict
*/

/**
* Returns true if the value acts like a Promise, i.e. has a "then" function,
* otherwise returns false.
*/
declare function isPromise(value: mixed): boolean %checks(value instanceof
Promise);

// eslint-disable-next-line no-redeclare
export default function isPromise(value) {
return Boolean(value && typeof value.then === 'function');
}
17 changes: 8 additions & 9 deletions src/jsutils/promiseReduce.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @flow strict
*/

import getPromise from './getPromise';
import isPromise from './isPromise';
import type { MaybePromise } from './MaybePromise';

/**
Expand All @@ -22,12 +22,11 @@ export default function promiseReduce<T, U>(
callback: (U, T) => MaybePromise<U>,
initialValue: MaybePromise<U>,
): MaybePromise<U> {
return values.reduce((previous, value) => {
const promise = getPromise(previous);
if (promise) {
return promise.then(resolved => callback(resolved, value));
}
// Previous is not Promise<U>, so it is U.
return callback((previous: any), value);
}, initialValue);
return values.reduce(
(previous, value) =>
isPromise(previous)
? previous.then(resolved => callback(resolved, value))
: callback(previous, value),
initialValue,
);
}