Z
Zodā€¢4d ago
Dawson

Dawson - Can someone sanity check this for me? ...

Can someone sanity check this for me? I'm trying to use an async refinement to validate name uniqueness, but the parse is always succeeding:
z.string()
.min(1)
.refine(async name => {
return false
// const result = await checkNameAvailability(name)
// return !!result?.isAvailable
})
.safeParseAsync('test')
.then(console.log)
z.string()
.min(1)
.refine(async name => {
return false
// const result = await checkNameAvailability(name)
// return !!result?.isAvailable
})
.safeParseAsync('test')
.then(console.log)
logs {success: true, data: 'test'}
16 Replies
Dawson
DawsonOPā€¢4d ago
simply removing the async keyword gives an expected result, but I obviously can't do that if I need an async call to perform the validation ok so it's working with superRefine, so maybe I'll use that ā€“ am I using refine wrong or is this a bug?
z.string()
.min(1)
.superRefine(async (name, ctx) => {
ctx.addIssue({
code: 'custom',
})
return false
// const result = await checkNameAvailability(name)
// return !!result?.isAvailable
})
.safeParseAsync('test')
.then(console.log)
z.string()
.min(1)
.superRefine(async (name, ctx) => {
ctx.addIssue({
code: 'custom',
})
return false
// const result = await checkNameAvailability(name)
// return !!result?.isAvailable
})
.safeParseAsync('test')
.then(console.log)
logs {success: false}
Scott Trinh
Scott Trinhā€¢4d ago
Hmmm... I cannot repro locally with:
async function main() {
const schema = z.string().refine(async () => false);
const result = await schema.safeParseAsync("boop");
console.log(result);
}

main()
async function main() {
const schema = z.string().refine(async () => false);
const result = await schema.safeParseAsync("boop");
console.log(result);
}

main()
It logs { success: false, error: [Getter] }
Dawson
DawsonOPā€¢4d ago
caution: strange behavior ahead šŸ˜… I get expected behavior in the node REPL:
āžœ yarn node
Welcome to Node.js v22.9.0.
Type ".help" for more information.
> const {z} = require("zod")
undefined
> z.string().refine(async name => false).safeParseAsync('test').then(console.log)
Promise {
<pending>,
[Symbol(async_id_symbol)]: 57,
[Symbol(trigger_async_id_symbol)]: 53
}
> { success: false, error: [Getter] }
āžœ yarn node
Welcome to Node.js v22.9.0.
Type ".help" for more information.
> const {z} = require("zod")
undefined
> z.string().refine(async name => false).safeParseAsync('test').then(console.log)
Promise {
<pending>,
[Symbol(async_id_symbol)]: 57,
[Symbol(trigger_async_id_symbol)]: 53
}
> { success: false, error: [Getter] }
but running the exact same code in my vite + react project I get the incorrect log in the devtools console:
{success: true, data: 'test'}
{success: true, data: 'test'}
so seemingly not an issue with zod but maybe with vite or react with zod some notes: - the issue happens whether the statement is executed in a react component/hook body or at the file top-level - I'm running the node REPL with yarn node so it should be using the same zod version - I'm running vite in dev mode with HMR and all that, so I'll try a production build and see what happens
Scott Trinh
Scott Trinhā€¢4d ago
šŸ¤Æ Can you share some of the exact code you're testing here? You're not going through a form library or anything like that, right? (exact code in the React/Vite context)
Dawson
DawsonOPā€¢4d ago
I use RHF elsewhere but I've been testing this outside of that usage ā€“ I'll see if I can get a barebones react repro up somewhere
Scott Trinh
Scott Trinhā€¢4d ago
Well, React seems relatively well-behaved here: https://codesandbox.io/p/sandbox/lxgljx
Dawson
DawsonOPā€¢4d ago
I tracked down the mismatch to this condition in the refine logic: https://github.com/colinhacks/zod/blob/f7ad26147ba291cb3fb257545972a8e00e767470/src/types.ts#L381C45-L381C70 with this code, my project logs false in devtools but everywhere else it seems to log true (repl, codesandbox, etc.):
console.log((async () => false)() instanceof Promise)
console.log((async () => false)() instanceof Promise)
I'll update here as I find things out, but at a certain point I'll need to move on just use superRefine instead :sadkragg: the instanceof Promise check is apparently sometimes faulty depending on transpilation stuff or if we're using a custom library for promises ā€“ not sure what it is for me, but it seems to be the case that I'm not using a native promise. regardless, would it be more helpful for zod to simply check whether the result is "thenable"? as it's not doing anything more sophisticated under the hood, and that's more in line with the Promises/A+ spec: https://promisesaplus.com/#point-53 I can get a small PR up to show what I mean if that's an option
Scott Trinh
Scott Trinhā€¢4d ago
PRs welcome, especially with a repro test case! To be fair, I sincerely hope no one has to polyfill Promise in the year 2024!
janglad
jangladā€¢3d ago
Heh this was interesting but also I know someone who works on TV apps and for some environments he has to target pre es6 so they def are out there :KEKW: hell might even be pre 5 on some I'm not sure
Scott Trinh
Scott Trinhā€¢3d ago
I feel like there are more fundamental things in the Zod codebase that would break at that point šŸ˜…
janglad
jangladā€¢3d ago
haha yup for sure
Dawson
DawsonOPā€¢3d ago
found the culprit! the way we set up otel, specifically with zone.js, is screwing with the promise prototype for async function returns ā€“ removing the context manager line fixes the issue:
const provider = new WebTracerProvider({ resource })

provider.addSpanProcessor(
new BatchSpanProcessor(
new OTLPTraceExporter({
url: import.meta.env.VITE_OTLP_TRACE_EXPORTER_URL,
}),
),
)

provider.register({
propagator: new W3CTraceContextPropagator(),
contextManager: new ZoneContextManager(), // THIS LINE
})
const provider = new WebTracerProvider({ resource })

provider.addSpanProcessor(
new BatchSpanProcessor(
new OTLPTraceExporter({
url: import.meta.env.VITE_OTLP_TRACE_EXPORTER_URL,
}),
),
)

provider.register({
propagator: new W3CTraceContextPropagator(),
contextManager: new ZoneContextManager(), // THIS LINE
})
- https://github.com/open-telemetry/opentelemetry-js/issues/3030 - https://github.com/angular/angular/issues/51328#issuecomment-1688604566 otel's ZoneContextManager seems to not support es2017+ from what I'm reading
GitHub
Using ZoneContextManager and FetchInstrumentation in browser causes...
What version of OpenTelemetry are you using? "@opentelemetry/api": "^1.1.0", "@opentelemetry/context-zone": "^1.3.0", "@opentelemetry/core": "...
GitHub
zone.js promises returning Unhandled Promise Rejection error even w...
Which @angular/* package(s) are the source of the bug? zone.js Is this a regression? No Description I've hit the same issue as described in #31680 and both this and that ticket have same root c...
Scott Trinh
Scott Trinhā€¢3d ago
šŸ˜±
Scott Trinh
Scott Trinhā€¢3d ago
GitHub
zone.js promises returning Unhandled Promise Rejection error even w...
Which @angular/* package(s) are the source of the bug? zone.js Is this a regression? No Description I've hit the same issue as described in #31680 and both this and that ticket have same root c...
Scott Trinh
Scott Trinhā€¢3d ago
Zone.js doesn't work with async/await. That's why angular uses a babel plugin to transform async/await into generator functions.
Dawson
DawsonOPā€¢2d ago
all that and I need to use superRefine anyway as that's the only way I can abort early šŸ˜© https://github.com/colinhacks/zod/issues/3884
GitHub
Feature Request: Add abortEarly Mode to Stop Validation at First E...
Zod is a fantastic validation library, but many developers, myself included, face performance challenges when validating data with multiple rules, especially for expensive operations like database ...
Want results from more Discord servers?
Add your server