I'm using flow 0.41.0.
I am trying to make some helpers around thefetch function that all have the same params and return type. In order to avoid boilerplate with regards to the types, I tried to use *, and typeof rather liberally to get Flow to do the work for me.
// @flow
import fetch from 'node-fetch'
export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
...opts
})
function fetchJSON(url: string | Request, opts?: RequestOptions = {}): Promise<Object> {
return fetch(url, opts).then(r => r.json())
}
The above post function, will throw an error regardless of the params you pass to it. For example, with the current params above, I get the errors
x.js:5
5: export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
^ object literal
6: method: 'POST',
^^^^^^ string. This type is incompatible with
824: method?: ?MethodType;
^^^^^^^^^^^ null. See lib: /private/tmp/flow/flowlib_316468aa/bom.js:824
x.js:5
5: export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
^ object literal
7: headers: { 'Content-Type': 'application/json' },
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ object literal. This type is incompatible with
822: headers?: ?HeadersInit;
^^^^^^^^^^^^ null. See lib: /private/tmp/flow/flowlib_316468aa/bom.js:822
x.js:5
5: export const post: typeof fetchJSON = (url: *, opts: *): * => fetchJSON(url, {
^ object literal
7: headers: { 'Content-Type': 'application/json' },
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `Content-Type`. Property not found in
822: headers?: ?HeadersInit;
^^^^^^^^^^^ Headers. See lib: /private/tmp/flow/flowlib_316468aa/bom.js:822
Found 3 errors
Doing flow type-at-pos for the post function, gives me
(url: string | Request, opts?: {
body?: ?(string | Blob | FormData | URLSearchParams),
cache?: ?'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload',
credentials?: ?'include' | 'omit' | 'same-origin',
headers?: ?{[key: string]: string,} | Headers,
integrity?: ?string,
method?: ?MethodType,
mode?: ?'cors' | 'no-cors' | 'same-origin',
redirect?: ?'error' | 'follow' | 'manual',
referrer?: ?string,
referrerPolicy?: ?'' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin-only' | 'origin-when-cross-origin' | 'unsafe-url'
}) => Promise <Object>
which seems correct to me. Is this a bug in Flow itself?
I'm not sure but this sure looks like a problem because of property variance.
Objects are invariant & you can see that flow is inferring types that differ. You just need to be a bit more explicit here. For example { method: string } is actually a supertype of { method?: ?MethodType } and a function that would accept this (write only) would need to be marked as contravariant.
I was able to reproduce this with a much smaller subset of the bigger problem, and I think I understand the problem now because it's completely unrelated to fetch itself.
// @flow
const opts: RequestOptions = {}
const test = { method: 'POST', headers: { 'Content-Type': 'application/json' }, ...opts }
The above causes similar errors, and it's because spreading opts into the object might rewrite 'POST' to null, which isn't allowed because it has a string type. The solution is to spread into a new object, instead of the existing one. So
const test = { ...{ method: 'POST', headers: { 'Content-Type': 'application/json' } }, ...opts }
will work. My bad here there's no bug. Thanks @nmn and @mwalkerwells.
Most helpful comment
I was able to reproduce this with a much smaller subset of the bigger problem, and I think I understand the problem now because it's completely unrelated to
fetchitself.The above causes similar errors, and it's because spreading
optsinto the object might rewrite'POST'tonull, which isn't allowed because it has astringtype. The solution is to spread into a new object, instead of the existing one. Sowill work. My bad here there's no bug. Thanks @nmn and @mwalkerwells.