You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionf(request: {params: {[param: string]: unknown;}}){letx=request.params.x;try{x=validate(x);}catch(e){}try{takesString(x);// Fails to compile, x is of type unknown.}catch(e){}}functionvalidate(x: unknown): string{return"";}functiontakesString(s: string){}
π Actual behavior
In this example, the code fails to compile because tsc has deduced x in the second block to be of type unknown
π Expected behavior
Since we have successfully exited the first try-catch block, x's type should be narrowed to string in the second.
Anything is assignable to unknown, but unknown isnβt assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.
In your case you can use any to say I support any value. And then all works!
functionf(request: {params: {[param: string]: any;}}){letx=request.params.x;try{x=validate(x);}catch(e){}try{takesString(x);// Fails to compile, x is of type unknown.}catch(e){}}functionvalidate(x: any): string{return"";}functiontakesString(s: string){}
We lose type safety with any. In my case the type of params is used for all of our server's endpoint handlers, so using unknown is useful to make sure we don't skip a validation step. I think the true correct way to do this would be to do something like
functionvalidate(x: unknown): x is string{// blah blah blah...}// ...functionf(request: {params: {[param: string]: unknown;}}){letx=request.params.x;if(!validate(x)){throwSomeError();}// now x is narrowed to `string`}
using unknown is useful to make sure we don't skip a validation step
I think you are right. Here is anohter example that ilustrates it: Playground
functionvalidate(x: unknown): x is string{returntypeofx==="string";}functionmayBeNorS(): number|string{if(Math.random()>0.5)return1;return'x'}// ...functionf(request: {params: {[param: string]: unknown;}}){letx=request.params.x;if(!validate(x)){throwError('some');}// now x is narrowed to `string`x="other";// now x is ruined to `unknown`x.toLocaleLowerCase();// if the original variable was not `unkonwn` it works!lety=mayBeNorS();y="other";// now y is narrowed to `string`y.toLocaleLowerCase();}
Activity
JonathanDCohen commentedon Apr 7, 2021
I've discovered that
also fails to compile. So maybe the issue is related to narrowing the type of a variable in a broader scope?
[-]Variable type isn't propagated out of a try block[/-][+]`unknown` isnβt narrowed by assignment[/+]andrewbranch commentedon Apr 9, 2021
I think this is probably a better example of what youβre surprised by, if Iβm understanding you.
JonathanDCohen commentedon Apr 9, 2021
emilioplatzer commentedon Apr 22, 2021
I my opinion you are confused about
unknown. If you see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type you can read:In your case you can use
anyto say I supportanyvalue. And then all works!Look at Playground
JonathanDCohen commentedon Apr 22, 2021
We lose type safety with
any. In my case the type ofparamsis used for all of our server's endpoint handlers, so usingunknownis useful to make sure we don't skip a validation step. I think the true correct way to do this would be to do something likewhich is what I've switched to in my code.
emilioplatzer commentedon Apr 22, 2021
I like your point:
I think you are right. Here is anohter example that ilustrates it: Playground
rChaoz commentedon Sep 28, 2024
Worth noting this also happens with
{}, not justunknown