flow 0.67.1 (but behavior continues to exist in 0.73.1)
Example:
type PropOptional = {
prop?: ComplexType
};
type ComplexType = {
callable: () => void,
anotherCallable: () => void
};
function usePropOptional(arg1: PropOptional) {
if (arg1.prop) {
arg1.prop.callable();
arg1.prop.anotherCallable();
arg1.prop.callable();
}
};
The function checks for the presence of arg1.prop before accessing any properties on arg1.prop. This should be sufficient to verify that arg1.prop is defined.
Flow is fine with the first time an arg1.prop property is accessed, which is the call to arg1.prop.callable() on the first line inside the if block. However, flow generates errors on subsequent attempts to access arg1.prop properties in the exact same if block:
arg1.prop.anotherCallable();
arg1.prop.callable();
I am forced to either prepend each line with a rote arg1.prop && truthy check, or reassign arg1.prop to a local variable inside the if block:
function usePropOptional(arg1: PropOptional) {
if (arg1.prop) {
const reallyExists = arg1.prop;
reallyExists.callable();
reallyExists.anotherCallable();
reallyExists.callable();
}
};
This doesn't feel right. What am I doing wrong or missing?
You can check this in a flow repl here on flow.org.
This is documented in FlowType's Type Refinement section:
Refinement Invalidations
It is also possible to invalidate refinements, for example:
// #flow
function otherMethod() { /* ... */ }
function method(value: { prop?: string }) {
if (value.prop) {
otherMethod();
// $ExpectError
value.prop.charAt(0);
}
}
The reason for this is that we don’t know that otherMethod() hasn’t
done something to our value.
...
There’s a straightforward way to get around this. Store the value
before calling another method and use the stored value instead. This
way you can prevent the refinement from invalidating.
// #flow
function otherMethod() { /* ... */ }
function method(value: { prop?: string }) {
if (value.prop) {
var prop = value.prop;
otherMethod();
prop.charAt(0);
}
}
So the workaround in your final case appears to be the suggested way to avoid this problem.
Related
Let's assume that I've the following object:
let filters = {
brands: { ... },
price: { ... },
sizes: { ... },
...
}
The properties of the filters object will be set by the users. Which means sometimes the filters object may contain just brands, sometimes it may contain brands & price and so on.
I've written the following function to extract a specific property from the filters object:
let extractProperty = (propertyName) => {
({ propertyName, ...rest } = filters); // <-- propertyName isn't working here
console.log(propertyName);
}
extractProperty("brands");
If I invoke the above function, the console displays undefined.
Can anyone please point me out what I'm missing here?
CodePen Link
Note:
I've already resolved this issue using lodash.omit method. But I'm still curious to know why function parameter value isn't working in object-destructuring.
Not Duplicate:
This question is about passing default value
That code is looking for a property called propertyName, literally. To use the value in propertyName as the property name, you need to use computed notation, and you'll need to specify where to put the property's value. For instance, to put it in an existing example variable:
let extractProperty = (propertyName) => {
({ [propertyName]: example, ...rest } = filters);
// ^−−−−−−−−−−−−^^^^^^^^^^
console.log(example);
};
extractProperty("brands");
Your code is written assuming that rest already exists, but I suspect you really want to declare it locally, along with the variable to receive the property value:
let extractProperty = (propertyName) => {
const { [propertyName]: example, ...rest } = filters;
console.log(example);
};
extractProperty("brands");
Without const, let (or var, but var is deprecated), unless you have rest declared in an enclosing scope, that code will either fail with a ReferenceError (in strict mode) or fall prey to what I call The Horror of Implicit Globals (in loose mode), automatically creating a global variable.
why use destructuring here when you just want to get a property?
let filters = {
brands: { value:'b' },
price: { value:'p' },
sizes: { value:'s' },
}
let extractProperty = propertyName =>
console.log(filters[propertyName])
extractProperty("brands");
Wish to enact upon arguments but with defaults defined
In my quest for self-documenting code with destructuring but being DRY wanting to do this...
async function shampoo({ lather = true, rinse = true, repeat = 2 } = {}) {
await api.dogWasherMachine(magicalArguments) // ???
// don't want to have to do this:
// api.dogWasherMachine({ lather, rinse, repeat })
// currenty arguments is {} and actual input is defined
}
How do I get these magical arguments that are defined?
arguments do not have the default input defined but how can I do this?
It's not possible to do it in the parameters alone - destructuring necessarily extracts each property into an independent named variable, without leaving a reference to the original object behind. You'll have to do it with another statement, eg:
async function shampoo(param = {}) {
const defaultObj = {
lather: true,
rinse: true,
repeat: 2
};
await api.dogWasherMachine({ ...defaultObj, ...param });
}
I use destructuring assignment to to self document inheritable classes that are used as interfaces. On construction I get all of the benefits of intellisense and when calling an API everything stays nice and DRY.
class washable {
constructor({ lather = true, rinse = true, repeat = 2 } = {}) {
this.lather = lather
this.rinse = rinse
this.repeat = repeat
}
}
class dog extends washable {
async shampoo() {
await api.dogWasherMachine(this)
}
}
I'm starting to delve into generics and have a generic event class that looks like this
export interface Listener < T > {
(event: T): any;
}
export class EventTyped < T > {
//Array of listeners
private listeners: Listener < T > [] = [];
Attach(listener: Listener < T > ) {
this.listeners.push(listener);
}
Emit(event: T) {
this.listeners.forEach(listener => listener(event));
}
}
I create my event like this onPageSizeSelected = new EventType<PageSizeSelector>();
My listeners signature is this PageSizeSelectedHandler(event:Event,object:PageSizeSelector).
When I go to attach the event like this pageSizeSelector.onPageSizeSelected.Attach(this.PageSizeSelectedHandler.bind(this)) no error is thrown.
When the handler is attached like this pageSizeSelector.onPageSizeSelected.Attach(this.PageSizeSelectedHandler). It immediately picks up the the method signature is incorrect and has too many parameters.
What is bind doing that typescript can't correctly infer the method signature? How can I safely keep my this and have my event strongly typed?
If all you want is for the compiler to catch bound methods having the wrong number of parameters and you don't care about the this context, you can just make sure to enable the --strictBindCallApply compiler option:
class StringListeningClassThing {
myString = "hey";
oneParam(x: string) {
return x + this.myString;
}
twoParams(x: number, y: string) {
return x.toFixed(2) + y + this.myString;
}
}
const onPageSizeSelected = new EventTyped<string>();
const stringListenerThingy = new StringListeningClassThing();
onPageSizeSelected.Attach(
stringListenerThingy.twoParams); // error
onPageSizeSelected.Attach(
stringListenerThingy.twoParams.bind(stringListenerThingy)); // error
onPageSizeSelected.Attach(
stringListenerThingy.oneParam.bind(stringListenerThingy)); // okay
onPageSizeSelected.Attach(
stringListenerThingy.twoParams.bind(stringListenerThingy, 2)); // okay
This might be all you need. But there are still some type safety issues here:
Unfortunately TypeScript doesn't do a great job of type-checking this contexts automatically:
onPageSizeSelected.Attach(
stringListenerThingy.oneParam); // no error
onPageSizeSelected.Attach(
stringListenerThingy.oneParam.bind({ notGood: true })); // no error
The fact that those are accepted means you will have runtime errors, as stringListenerThingy's methods dereference a bad this.
There is a suggestion at microsoft/TypeScript#7968 to add something like a --strictThis compiler option which would prevent you from passing around mis-bound functions, but it hasn't yet been implemented, apparently because it would both break lots of existing code and have a significant compiler performance impact. If you want to see that implemented you might want to go to that issue and give it a 👍 and/or describe your use case (if it is particularly compelling and not already mentioned in that issue).
If you really want to make the compiler do this checking, it is possible, but you will need to add this parameters manually to all sorts of places in your code. For example, you could do something like this:
// explicitly add void this-context to definition of Listener
export interface Listener<T> {
(this: void, event: T): any;
}
// explicitly add class-based this-context to all methods
class StringListeningClassThing {
myString = "hey";
oneParam(this: StringListeningClassThing, x: string) {
return x + this.myString;
}
twoParams(this: StringListeningClassThing, x: number, y: string) {
return x.toFixed(2) + y + this.myString;
}
}
And now the above examples give the desired errors:
// enjoy type safety
onPageSizeSelected.Attach(
stringListenerThingy.oneParam); // error
onPageSizeSelected.Attach(
stringListenerThingy.oneParam.bind({ notGood: true })); // error
So the compiler can enforce this stuff, but not automatically, until and unless --strictThis becomes a thing.
Playground link to code
I have a function like this:
private myFunc = (myNumber: number) => {
if (myNumber === 1) {
console.log('here');
}
}
Where myNumber is 1, I don't get the console output. From having a look at the console, I can see that myNumber is being treated as a different type (string). Changing the code like this works:
private myFunc = (myNumber: number) => {
if (myNumber == 1) {
console.log('here');
}
}
I was under the impression that Typescript would issue a 'compile' error in this case, but it doesn't seem to. Can anyone tell me why?
Yes, Typescript would show a compile time error if you'd do:
myFunc("1");
However, as you seem to call it at runtime, Typescript can't check for it.
Typescript use a static type checking ( typescript is not present at the runtime ) so if you pass a string to the function 'myNumber' will be a string. You need to add your own check inside the function
private myFunc = (myNumber: number) => {
if (parseInt(myNumber) === 1) {
console.log('here');
}
}
I am try to make a logging service for my TypeScript / Angular 2 App. Unfortunately if i call console.log the line number is wrong. Even if i try to return console.log().
Here is my code:
LoggerService.ts
export class LoggerService {
log(message) {
// Server-side logging
// [...]
if (clientSideLogging) return console.log(message);
}
}
SomewhereElse.ts
this.logger.log('hello world');
-> Shows line number of LoggerService.ts instead of source
You could use the .bind() method to bind window.console to your custom log method and then return the function so that the code is executed within the original scope when it is called.
In doing so, the line number will be preserved when calling the logger service's log method:
class LoggerService {
public log = console.log.bind(window.console);
}
// ...or annotated:
class LoggerService {
public log: (message) => void = console.log.bind(window.console);
}
Then if you want to add in your conditional statement:
class LoggerService {
public log = clientSideLogging ? console.log.bind(window.console) : () => {};
}
Here is an example with the compiled TypeScript code.
Aside from the one-liner solutions mentioned above, if you want to implement additional logic inside of the log method, then you could utilize a getter which will return and call the console.log function that is bound to window.console.
class LoggerService {
public get log (): Function {
// Implemnt server-side logging
return console.log.bind(window.console);
}
}
As you can tell, it is important for the console.log function to be returned since it will not preserve the line numbers when it is called directly within another scope.
Then if you want to add in your conditional statement:
class LoggerService {
public get log (): Function {
const log = console.log.bind(window.console);
// Implemnt server-side logging
return clientSideLogging ? log : () => {};
}
}
Here is an example with the compiled TypeScript code.
You could use .trace() instead of .log().
this.logger.trace('hello world');
This will give you a stack trace to the original line number.
https://developer.mozilla.org/en-US/docs/Web/API/Console/trace