Async scalars with access to `context` and `info`?
See original GitHub issueIn our resolver map, we have two quite different things:
- Resolvers, which are functions called by graphql-js with useful objects like
infoorcontext, and they are usually async. - Custom scalars, which are objects, don’t have access to
infoorcontext(or anything request-related) and their functions likeserializearen’t async.
I wonder if scalars could be more resolver-like?!
Concrete example
Our use case is price rounding. In our case, GraphQL server code doesn’t know how to round – it needs to ask a backend API for rounding rules – but it is GraphQL code’s responsibility to ensure that values are rounded.
Most prices in our schema are represented by a Money type that looks like this:
type Money {
amount: MonetaryAmount! # custom scalar
currencyCode: String!
}
We can then do something like this, which works well:
const resolvers = {
Query: {
Money: {
amount: async (money, _, { dataSources: { backendApi } }) =>
round(money.amount, money.currencyCode, backendApi),
},
}
};
async function round(amount, currency, backendApi) {
const roundingInfo = await backendApi.getRoundingInfo(...);
// etc.
}
Some parts of our schema, however, use the scalar directly, skipping the Money type. For example:
type GoogleEcommerceDataLayerActionField {
id: String!
revenue: MonetaryAmount
tax: MonetaryAmount
}
I’d love to be able to write resolver-like code for it, like this:
// pseudocode!
const resolvers = {
__customScalars: {
MonetaryAmount: async (value, { config, dataSources: { backendApi } }) =>
round(value, config.currencyCode, backendApi),
},
};
Currently, we need to solve this on the specific field level, for example, if there’s a field like:
type GoogleEcommercePurchaseDataLayer {
actionField: GoogleEcommerceDataLayerActionField
}
we can hook into actionField and process the rounding there. But it would be safer and easier if we could solve it at the type level.
What do you think? Has this been considered before? I didn’t find much but maybe my search skills have failed me.
Issue Analytics
- State:
- Created 3 years ago
- Comments:16 (10 by maintainers)

Top Related StackOverflow Question
We use
serializefor printing schemas with default values and also for sending default values inside introspection. So we need to also keep it request-independent.Yes, it true for both parsing/printing SDL and sending/receiving introspection results.
Theoretically yes, practically it would result in very strange API where a function doesn’t receive most of the parameters and can respond with a promise only in certain situations.
Moreover awaiting inside every
serializewill kill your performance since everyawait(even if the promise is already resolved) goes through microtasks queue, what’s even worse is that by making more leaves return promises you forcing more of execution to be done in async mode. It will totally kill performance if you returning big arrays of such scalars, for context please see: https://github.com/graphql/graphql-js/issues/723#issuecomment-383346375 So what’s why we limiting async to only functions that absolutely need it likeresolveandresolveType.What you can do is to use wrappers for numbers that you want to wrap and address rounding it before or during serilization:
Note that the use case in the related ticket #3539 relates to customizing error message languages, with the parse/serialize functions the normal place where these errors and messages are generated.
The implementation within #3600 that allows access to
contextis meant for only scalar-specific work such as the above. For that reason, the functions remain synchronous, and access toinfois not provided.