FlowJS and function with 2 signatures and optional parameter - flowtype

How do I precisely type in Flow the following function that either takes a callback and call it later with some value or takes no arguments and return a Promise for that value?
const foo = callback => {
const p = Promise.resolve(1.0);
if (callback === undefined) {
return p;
}
p.then(callback);
}
};
I tried to use an intersection type like in:
type CallbackCase = ((number) => void) => void;
type PromiseCase = () => Promise<number>;
const foo: CallbackCase & PromiseCase =
callback => {
const p = Promise.resolve(1.0);
if (callback === undefined) {
return p;
}
p.then(callback);
};
But then Flow complains (this is also so with the latest version at https://flow.org/try):
Cannot assign function to `foo` because undefined [1] is incompatible with `Promise` [2] in the return value. [incompatible-type]
So how can I fix this?
Update: The reason for this signature is that we have older code where the function was taking a callback. We would like to convert it to the promise form while still supporting older callers. During the conversion we would like to keep the types precise. In particular at the call site only the two forms should be allowed:
let a: Promise<number> = foo();
foo(callback);
Any other forms should be rejected.

You can specify argument as a function
type Callback = <T>(T) => void;
const foo =
(callback:Callback) => {
const p = Promise.resolve(1.0);
if (callback === undefined) {
return p;
}
p.then(callback);
};
seems to works well

Related

Trouble reading documentation Interface Definition

I'm trying to figure out how a Redux createStore function works(what parameters it accepts) with enhancers from the documentation.
what I understand is, "sayHiOnDispatch" takes a "createStore" function as a parameter and creates a closure around the inner anonymous function which accepts 3 arguments,
rootReducer, preloadedState, enhancers
finally, it return an object {...store,dispatch:newDispatch}.
what I don't understand is: 1)Where is sayHiOnDispatch is being called from?
2)How the anonymous function is getting called?
3)What variable receives the return value of return { ...store, dispatch: newDispatch }
4)What calls the newDispatch functions?
5)How can I understand the function structure(params, return values, etc..) from the Interface Definition?
export type StoreEnhancer<Ext = {}, StateExt = never> = (
next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
S = any,
A extends Action = AnyAction
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
The redux tutorial code.
export const sayHiOnDispatch = (createStore) => {
return (rootReducer, preloadedState, enhancers) => {
const store = createStore(rootReducer, preloadedState, enhancers)
function newDispatch(action) {
const result = store.dispatch(action)
console.log('Hi!')
return result
}
return { ...store, dispatch: newDispatch }
}
}
Hopefully, someone will provide a fishing-rod for me to fish the fish.
Answering the questions in order:
sayHiOnDispatch gets called either as return enhancer(createStore)(reducer, preloadedState) inside of createStore itself, or on the same line as part of a "composed" enhancer (like compose(applyMiddleware(), sayHiOnDispatch) )
The anonymous function is called on that same line - it's the return value from enhancer()
The returned {...store, dispatch: newDispatch} is the actual store variable, as in const store = createStore()
newDispatch is the actual store.dispatch function, so it's called as store.dispatch(someAction)
Yeah, that is definitely a complex type signature :) To be honest I wouldn't worry about it - odds are you won't ever end up writing an enhancer yourself.

Flow property is missing in mixed passed - despite the type annotation

I have the following in my declarations file (included in my [libs]):
export type EtlFieldNoIdxT = {
name: Name,
purpose: Purpose,
}
export type EtlFieldT = {
idx: number,
...EtlFieldNoIdxT
}
And the following in my use of the types:
export const createEtlField = (
etlFields: { [Name]: EtlFieldT },
newField: EtlFieldNoIdxT,
) => {
if (etlFields === {}) {
throw new Error({
message: 'Cannot create a new etlField with an empty etlFields',
});
}
const field: EtlFieldT = {
idx: maxId(etlFields, 'idx') + 1,
...newField,
};
const subject: Name = Object.values(etlFields).find(
(f) => f.purpose === 'subject', // <<< f.purpose "missing in mixed" error
).name; // <<< .name "missing in mixed" error
return newEtlField(field, subject);
};
Despite having annotated the input, can flow not infer the type of what Object.values would thus return?
Thank you in advance for pointing out my misunderstanding.
- E
If you check the declaration for Object.values you'll find that it returns an array of mixed:
static values(object: $NotNullOrVoid): Array<mixed>;
A quick google search came back with
https://davidwalsh.name/flow-object-values
So to solve your issue, you wrap Object.values(...) with any, and then inside your find arg you can type it as EtlFieldT and finally refine your type back to EtlFieldT after find.
const subject: Name = ((Object.values(etlFields): any).find(
(f: EtlFieldT) => f.purpose === 'subject',
): EtlFieldT).name;
Though you should be aware that find has the possibility of returning undefined. So to be sound, you should run the find, and declare subject if the value exists.

Flow errors when dealing with nullable types

I have working on a redux reducer with the following state:
export type WishlistState = {
+deals: ?DealCollection,
+previousWishlist: ?(Deal[]),
+currentWishlist: ?(Deal[]),
+error: ?string
};
export type DealCollection = { [number]: Deal };
export const initialState: WishlistState = {
deals: null,
previousWishlist: null,
currentWishlist: null,
error: null
};
export default function wishlistReducer(
state: WishlistState = initialState,
action: WishlistAction
): WishlistState {
switch (action.type) {
case "GET_DEALS_SUCCESS":
return { ...state, deals: action.deals };
case types.GET_WISHLIST_SUCCESS:
console.log(action);
const currentWishlist: Deal[] = action.wishlistIds.map(
// ATTENTION: THIS LINE HERE
d => state.deals[d]
);
return {
...state,
currentWishlist,
previousWishlist: null,
error: null
};
// ...other cases
default:
return state;
}
}
The line I've flagged with the comment is getting a flow error on the d in the
brackets:
Cannot get `state.deals[d]` because an index signature declaring the expected key/value type is missing in null or undefined.
This is happening because of the type annotation: deals: ?DealCollection, which is made clearer if I change the line to this:
d => state.deals && state.deals[d]
Which moves the error to state.deals; and the idea is that if state.deals is null, then the callback returns null (or undefined), which is not a acceptable return type for a map callback.
I tried this and I really thought it would work:
const currentWishlist: Deal[] = !state.deals
? []
: action.wishlistIds.map(d => state.deals[d]);
It would return something acceptable if there are no deals is null, and never get to the map call. But this puts the error back on the [d] about the index signature.
Is there any way to make Flow happy in this situation?
Flow invalidates type refinements whenever a variable may have been modified. In your case, the thought of checking !state.deals is a good start; however, Flow will invalidate the fact that state.deals must have been a DealCollection because (theoretically) you could be modifying it in your map function. See https://stackoverflow.com/a/43076553/11308639 for more information on Flow type invalidation.
In your case, you can "cache" state.deals when you have refined it as a DealCollection. For example,
type Deal = string; // can be whatever
type DealCollection = { [number]: Deal };
declare var deals: ?DealCollection; // analogous to state.deals
declare var wishlistIds: number[]; // analogous to action.wishlistIds
let currentWishlist: Deal[] = [];
if (deals !== undefined && deals !== null) {
const deals_: DealCollection = deals;
currentWishlist = wishlistIds.map(d => deals_[d]);
}
Try Flow
that way you can access deals_ without Flow invalidating the refinement.

How to type annotate "function wrappers" (function which returns a function with the same signature as it's argument)

Is there a way to properly tell flow that I'm returning a function with the same signature as the function I'm passed, but not exactly the same function ?
This is an example of a "once" wrapper which prevents a function from being called multiple times, it works but uses an any-cast internally to make flow give up, I'd like to get rid of that cast and have 100% coverage:
module.exports.once = /*::<F:Function>*/(f /*:F*/) /*:F*/ => {
let guard = false;
return ((function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
}/*:any*/) /*:F*/);
};
Okay, first things first.
Your return value can currently never match F without your casting through any because the signature of the function you're returning is not the same because it can return undefined where the original may not.
(comment syntax removed for readability)
module.exports.once = <F: Function>(f: F): F => {
let guard = false;
return ((function () { // this function returns the return value of F or void
if (guard) { return; } // returning void
guard = true;
return f.apply(null, arguments);
}: any): F);
};
But to start typing this, we're gonna need to break down that function generic a little bit.
First of all, let's not use Function as it's generally better if we don't:
However, if you need to opt-out of the type checker, and don’t want to go all the way to any, you can instead use Function. Function is unsafe and should be avoided.
Also, we're going to extract the types of the arguments and the return value so we can manipulate them independently and construct a return type. We'll call them Args and Return so they're easy to follow.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
) ((...Array<Args>) => Return | void) => { // note `Return | void`
let guard = false;
return function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
};
};
Now that we're taking into account that our new function might return void everything type checks fine. But of course, the return type of our once function will no longer match the type of the passed function.
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once(func); // error!
// Cannot assign `module.exports.once(...)` to `onceFunc` because
// undefined [1] is incompatible with string [2] in the return value.
Makes sense, right?
So, let's discuss the signature of this function. We want our return value to have the same signature as the function we pass in. Currently it doesn't because we're adding void to the signature. Do we need to? Why are we returning undefined? How can we always return the same type from our onced function? Well, one option would be to store the return value from the single call to the function and always return the stored return value for subsequent calls. This would kind of make sense because the whole point is to allow multiple calls but not perform any of the functions effects. So this way we can avoid changing the interface of the function, so we really don't need to know whether or not the function has been called before.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
): ((...Array<Args>) => Return) => {
let guard = false;
let returnValue: Return;
return function () {
if (guard) { return returnValue; }
guard = true;
returnValue = f.apply(null, arguments);
return returnValue;
};
};
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once2(func);
One good question to ask at this point would be, why do the types match even if we're not technically returning exactly F? The answer to that is because functions in flow are structurally typed. So if they have the same arguments and return value, their types match.

flowtype - higher order functions - currying

type hof = (f:Function) => Function;
type ty_MiddleWare = (store:StoreType) => hof;
How can I type higher order functions ? A function that returns a function that returns a function ?
I am trying to make this code typesafe and the above attempt is not accepted by flow. The problem comes up when using curryed functions.
this is how I indend to use the ty_MiddleWare:
const addLoggingToDispatch = (store) :ty_MiddleWare=>{
return (next:Function)=>{
return (action)=>{
console.group(action.type);
console.log('%c prev state','color: gray', store.getState());
console.log('%c action','color: blue',action);
const returnValue=next(action);
console.log('%c next state','color: green',store.getState());
console.groupEnd();
return returnValue;
};
};
};
Don't you mean:
const addLoggingToDispatch:ty_MiddleWare = (store) => { ?
The way you wrote it, addLoggingToDispatch returns your ty_MiddleWare type. In my way, you declare addLoggingToDispatch to be ty_MiddleWare

Resources