Reputation: 36826
I have a function that return object with two props (res, mes) where one of them is null:
const fetchJSON = <Res, Body>(link: string, body: Body): Promise<{ res: Res; mes: null } | { res: null; mes: Popup }> => {
return new Promise(resolve => {
fetch(link,
body: JSON.stringify(body)
})
.then(res => res.json())
.then((resJson: Res) => {
resolve({ res: resJson, mes: null })
})
.catch(err => {
resolve({ res: null, mes: { type: 'err', text: 'some error' } })
})
})
}
If after i use response of fetch without desctruction everything works fine:
const result = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { id })
if (result.mes) return popupPush(result.mes)
setProfile(result.res.reader)
But if i use object descruction:
const { res, mes } = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { readerId, role: 'alfa' })
if (mes) return popupPush(mes)
console.log(res.id)
Typescript doesn't understand that res is not null even if i checked mes:
Is there a way to fix this or i just need to forget about object destruction?
Or maybe there another approach for wrappers like this?
Upvotes: 3
Views: 789
Reputation: 7780
If you know that the property that you are dealing with won't be null
. You can use the non-null assertion operator (!
) to assert that it isn't null
or undefined
.
Example console.log(res!.id)
:
function getResult(type: 'res' | 'mes') {
let result: {
res?: { foo: string },
mes?: { bar: string },
} = {};
if (type === 'res') {
result.res = {
foo: 'res',
};
} else {
result.mes = {
bar: 'mes',
};
}
return result;
}
const { res, mes } = getResult('res');
console.log(res!.foo); // non-null assertion
console.log(mes?.bar); // optional chaining
Additionally, you can use the optional chaining operator (?
) to have the expression return null
if the preceding property is null
.
Upvotes: 1
Reputation: 21453
as far as I know this is a current limitation of typescript. at the time you deconstruct the type is { res: Res; mes: null } | { res: null; mes: Popup }
so when unpacking it has to consider all possibilities meaning both res
and messages
can be null.
After seperating the 2 variables there is no way for the union before to link the 2 variables, they are separate. I look forward to a time when this limitation can be overcome but as of now you are stuck with keeping guards within one object.
one possible solution if your data structures allow it is to just return one field that can be differentiated:
const fetchJSON = <Res, Body>(link: string, body: Body): Promise<{ res: Res; mes: null } | { res: null; mes: Popup }> => {
return new Promise(resolve => {
fetch(link,
body: JSON.stringify(body)
})
.then(res => res.json())
.then((resJson: Res) => {
resolve(resJson)
})
.catch(err => {
resolve({ type: 'err', text: 'some error' })
})
})
}
function isErrorMessage(obj: any): obj is Popup {
return obj && obj.type === "err";
}
async function MAIN(){
const result = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { id })
if (isErrorMessage(result)) return popupPush(result)
setProfile(result.reader)
}
Upvotes: 1