typescript - Kan inte få metodöverlagring att fungera tillsammans med diskriminerad union. Vad är fel?
Hur kan jag få följande kod att fungera utan as IUpdateRequestPromiseProxy<TEntity>-påståendet?
import { Id } from 'app/core/persistence';
import { IPersistence } from '..';
import { IBaseEntity } from '../../..';
/*
* Missing changeFn indicates delete
*/
type IChangeRequestPromiseProxy<TEntity extends IBaseEntity> =
| IUpdateRequestPromiseProxy<TEntity>
| IDeleteRequestPromiseProxy;
interface IUpdateRequestPromiseProxy<TEntity extends IBaseEntity> {
id: Id;
kind: 'update';
resolve(value: IPersistence<TEntity>): void;
reject(reason: any): void;
changeFn(entity: IPersistence<TEntity>): IPersistence<TEntity>;
}
interface IDeleteRequestPromiseProxy {
id: Id;
kind: 'delete';
resolve(value: void): void;
reject(reason: any): void;
}
export class QueueService<TEntity extends IBaseEntity> {
private changeRequestsQueue: Array<IChangeRequestPromiseProxy<TEntity>> = [];
private flushingQueueMutex: boolean = false;
public async enqueue(id: Id, kind: 'delete'): Promise<void>;
public async enqueue(
id: Id,
kind: 'update',
changeFn: (entity: IPersistence<TEntity>) => IPersistence<TEntity>,
): Promise<IPersistence<TEntity>>;
public async enqueue(
id: Id,
kind: 'delete' | 'update',
changeFn?: (entity: IPersistence<TEntity>) => IPersistence<TEntity>,
): Promise<IPersistence<TEntity> | void> {
let promise: Promise<IPersistence<TEntity> | void>;
switch (kind) {
case 'delete':
promise = new Promise<void>(async (resolve, reject) => {
this.changeRequestsQueue.push({
id,
kind,
resolve,
reject,
});
});
break;
case 'update':
default:
promise = new Promise<IPersistence<TEntity>>(async (resolve, reject) => {
this.changeRequestsQueue.push({
id,
kind,
resolve,
reject,
changeFn,
} as IUpdateRequestPromiseProxy<TEntity>);
});
break;
}
return promise;
}
}
Om jag tar bort påståendet får jag felet:
Argument of type '{ id: string; kind: "update"; resolve: (value?: IPersistence<TEntity> | PromiseLike<IPersistence<TEntity>> | undefined) => void; reject: (reason?: any) => void; changeFn: ((entity: IPersistence<TEntity>) => IPersistence<...>) | undefined; }' is not assignable to parameter of type 'IChangeRequestPromiseProxy<TEntity>'.
Type '{ id: string; kind: "update"; resolve: (value?: IPersistence<TEntity> | PromiseLike<IPersistence<TEntity>> | undefined) => void; reject: (reason?: any) => void; changeFn: ((entity: IPersistence<TEntity>) => IPersistence<...>) | undefined; }' is not assignable to type 'IUpdateRequestPromiseProxy<TEntity>'.
Types of property 'changeFn' are incompatible.
Type '((entity: IPersistence<TEntity>) => IPersistence<TEntity>) | undefined' is not assignable to type '(entity: IPersistence<TEntity>) => IPersistence<TEntity>'.
Type 'undefined' is not assignable to type '(entity: IPersistence<TEntity>) => IPersistence<TEntity>'.ts(2345)
Svar #1
Problemet är att TypeScript inte spårar relationen mellan kind och changeFn som skapas av överlagringarna. Så changeFn kan fortfarande vara undefined enligt TypeScript även om du befinner dig på grenen kind === 'update'.
Den enklaste lösningen är att använda en not null-assertion:
this.changeRequestsQueue.push({
id,
kind: 'update',
resolve,
reject,
changeFn: changeFn!,
});
Om du vill ha den fullständiga versionen utan påståenden kan det göras med en diskriminerad tuppel i parametrarna, även om jag inte är säker på att det är värt den extra komplexiteten:
export class QueueService<TEntity extends IBaseEntity> {
private changeRequestsQueue: Array<IChangeRequestPromiseProxy<TEntity>> = [];
private flushingQueueMutex: boolean = false;
public async enqueue(id: Id, kind: 'delete'): Promise<void>;
public async enqueue(
id: Id,
kind: 'update',
changeFn: (entity: IPersistence<TEntity>) => IPersistence<TEntity>,
): Promise<IPersistence<TEntity>>;
public async enqueue(
id: Id,
...r: ['delete'] | ['update', (entity: IPersistence<TEntity>) => IPersistence<TEntity>],
): Promise<IPersistence<TEntity> | void> {
let promise: Promise<IPersistence<TEntity> | void>;
switch (r[0]) {
case 'delete':
promise = new Promise<void>(async (resolve, reject) => {
this.changeRequestsQueue.push({
id,
kind: r[0],
resolve,
reject,
});
});
break;
case 'update':
default:
promise = new Promise<IPersistence<TEntity>>(async (resolve, reject) => {
this.changeRequestsQueue.push({
id,
kind: r[0],
resolve,
reject,
changeFn: r[1],
});
});
break;
}
return promise;
}
}
