From 23dd2940573135de324941b588bf7bb514651cc9 Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Thu, 26 Mar 2020 23:30:49 +0900 Subject: [PATCH] feat(performance): optimize resolvers execution paths (#488) --- src/resolvers/create.ts | 24 +++++----- src/resolvers/helpers.ts | 97 ++++++++++++++++++++------------------ src/utils/isPromiseLike.ts | 5 ++ 3 files changed, 70 insertions(+), 56 deletions(-) create mode 100644 src/utils/isPromiseLike.ts diff --git a/src/resolvers/create.ts b/src/resolvers/create.ts index b2f66c272..a31d8a3d7 100644 --- a/src/resolvers/create.ts +++ b/src/resolvers/create.ts @@ -9,6 +9,7 @@ import { getParams, applyMiddlewares, applyAuthChecker } from "./helpers"; import { convertToType } from "../helpers/types"; import { BuildContext } from "../schema/build-context"; import { ResolverData } from "../interfaces"; +import isPromiseLike from "../utils/isPromiseLike"; export function createHandlerResolver( resolverMetadata: BaseResolverMetadata, @@ -24,17 +25,23 @@ export function createHandlerResolver( const middlewares = globalMiddlewares.concat(resolverMetadata.middlewares!); applyAuthChecker(middlewares, authMode, authChecker, resolverMetadata.roles); - return async (root, args, context, info) => { + return (root, args, context, info) => { const resolverData: ResolverData = { root, args, context, info }; const targetInstance = container.getInstance(resolverMetadata.target, resolverData); - return applyMiddlewares(container, resolverData, middlewares, async () => { - const params: any[] = await getParams( + return applyMiddlewares(container, resolverData, middlewares, () => { + const params: Promise | any[] = getParams( resolverMetadata.params!, resolverData, globalValidate, pubSub, ); - return targetInstance[resolverMetadata.methodName].apply(targetInstance, params); + if (isPromiseLike(params)) { + return params.then(resolvedParams => + targetInstance[resolverMetadata.methodName].apply(targetInstance, resolvedParams), + ); + } else { + return targetInstance[resolverMetadata.methodName].apply(targetInstance, params); + } }); }; } @@ -86,13 +93,8 @@ export function createSimpleFieldResolver( const middlewares = globalMiddlewares.concat(fieldMetadata.middlewares!); applyAuthChecker(middlewares, authMode, authChecker, fieldMetadata.roles); - return async (root, args, context, info) => { + return (root, args, context, info) => { const resolverData: ResolverData = { root, args, context, info }; - return await applyMiddlewares( - container, - resolverData, - middlewares, - () => root[fieldMetadata.name], - ); + return applyMiddlewares(container, resolverData, middlewares, () => root[fieldMetadata.name]); }; } diff --git a/src/resolvers/helpers.ts b/src/resolvers/helpers.ts index c21eb9728..50c2b018a 100644 --- a/src/resolvers/helpers.ts +++ b/src/resolvers/helpers.ts @@ -8,55 +8,59 @@ import { ResolverData, AuthChecker, AuthMode } from "../interfaces"; import { Middleware, MiddlewareFn, MiddlewareClass } from "../interfaces/Middleware"; import { IOCContainer } from "../utils/container"; import { AuthMiddleware } from "../helpers/auth-middleware"; +import isPromiseLike from "../utils/isPromiseLike"; -export async function getParams( +export function getParams( params: ParamMetadata[], resolverData: ResolverData, globalValidate: boolean | ValidatorOptions, pubSub: PubSubEngine, -): Promise { - return Promise.all( - params - .sort((a, b) => a.index - b.index) - .map(async paramInfo => { - switch (paramInfo.kind) { - case "args": - return await validateArg( - convertToType(paramInfo.getType(), resolverData.args), - globalValidate, - paramInfo.validate, - ); - case "arg": - return await validateArg( - convertToType(paramInfo.getType(), resolverData.args[paramInfo.name]), - globalValidate, - paramInfo.validate, - ); - case "context": - if (paramInfo.propertyName) { - return resolverData.context[paramInfo.propertyName]; - } - return resolverData.context; - case "root": - const rootValue = paramInfo.propertyName - ? resolverData.root[paramInfo.propertyName] - : resolverData.root; - if (!paramInfo.getType) { - return rootValue; - } - return convertToType(paramInfo.getType(), rootValue); - case "info": - return resolverData.info; - case "pubSub": - if (paramInfo.triggerKey) { - return (payload: any) => pubSub.publish(paramInfo.triggerKey!, payload); - } - return pubSub; - case "custom": - return await paramInfo.resolver(resolverData); - } - }), - ); +): Promise | any[] { + const paramValues = params + .sort((a, b) => a.index - b.index) + .map(paramInfo => { + switch (paramInfo.kind) { + case "args": + return validateArg( + convertToType(paramInfo.getType(), resolverData.args), + globalValidate, + paramInfo.validate, + ); + case "arg": + return validateArg( + convertToType(paramInfo.getType(), resolverData.args[paramInfo.name]), + globalValidate, + paramInfo.validate, + ); + case "context": + if (paramInfo.propertyName) { + return resolverData.context[paramInfo.propertyName]; + } + return resolverData.context; + case "root": + const rootValue = paramInfo.propertyName + ? resolverData.root[paramInfo.propertyName] + : resolverData.root; + if (!paramInfo.getType) { + return rootValue; + } + return convertToType(paramInfo.getType(), rootValue); + case "info": + return resolverData.info; + case "pubSub": + if (paramInfo.triggerKey) { + return (payload: any) => pubSub.publish(paramInfo.triggerKey!, payload); + } + return pubSub; + case "custom": + return paramInfo.resolver(resolverData); + } + }); + if (paramValues.some(isPromiseLike)) { + return Promise.all(paramValues); + } else { + return paramValues; + } } export function applyAuthChecker( @@ -70,12 +74,15 @@ export function applyAuthChecker( } } -export async function applyMiddlewares( +export function applyMiddlewares( container: IOCContainer, resolverData: ResolverData, middlewares: Array>, resolverHandlerFunction: () => any, ): Promise { + if (middlewares.length === 0) { + return resolverHandlerFunction(); + } let middlewaresIndex = -1; async function dispatchHandler(currentIndex: number): Promise { if (currentIndex <= middlewaresIndex) { diff --git a/src/utils/isPromiseLike.ts b/src/utils/isPromiseLike.ts new file mode 100644 index 000000000..1a9743f3b --- /dev/null +++ b/src/utils/isPromiseLike.ts @@ -0,0 +1,5 @@ +export default function isPromiseLike( + value: PromiseLike | TValue, +): value is PromiseLike { + return value != null && typeof (value as PromiseLike).then === "function"; +}