我有一个名为isDone()
的函数,它返回与两个数据结构之一相同的信息(例如,哈希数组或哈希字典):
public async isDone() {
this.startDelayedTasks();
await Promise.all(this._tasks);
const hadErrors = this._failed.length > 0 ? true : false;
if (hadErrors) {
throw new ParallelError(this);
}
return this._resultType === "hash"
? (this._results as IDictionary<T>)
: hashToArray<IParallelArrayType<T>>(this._results) as IParallelArrayType<T>[];
}
其中IParallelArrayType为:
export interface IParallelArrayType<T> {
name: string;
value: T;
}
对于要求数组类型的消费者,他们可能希望运行标准的仅限数组的函数,例如map
,filter
,甚至仅length
,但因为仅返回类型< em>可能是一个数组,它会出现以下错误:
当然,消费者可以通过以下方式解决这个问题:
if(Array.isArray(results)) {
expect(results.length).to.equal(4);
}
但如果有某种方法可以避免消费者的这种负担,那将是非常好的。我想象的是标记的联合类型,并且可能使用符号作为属性?
看起来像这样的东西可能会让我在那里,但要真正让它闪耀我想要哈希/字典返回类型的消费者不要让Symbol键成为他们的标准迭代工具Object.keys(dictionary)
的一部分。这是可能的还是我接近运行时环境以使其工作?
答案 0 :(得分:1)
接下来作为答案。您提出的想法仍然需要消费者执行某种检查或断言来说服编译器返回的值是数组而不是字典。
减轻消费者负担的唯一方法是在将returnAsArray()
调用到保证{{1}的类型的版本后,确保流畅的界面类型更改返回一个数组。由于TypeScript不支持改变对象类型的想法,因此如果返回不同的对象,它是最好的。这里是代码的概念草图。它没有使用与您相同的类型或逻辑(同步和手动设置结果),但它应该给出如何实现它的图片:
isDone()
这里有两种结果类型,以及字符串type Dictionary<T> = { [k: string]: T }
type EntryArray<T> = { k: string, v: T }[];
type ResultType<T> = {
dict: Dictionary<T>;
array: EntryArray<T>;
}
和"dict"
到相应类型的映射。现在我们定义流利的类:
"array"
请注意class FluentThing<T, R extends keyof ResultType<T>=keyof ResultType<T>> {
readonly resultType: R;
results: Dictionary<T> = {};
constructor() {
this.resultType = "dict" as R;
}
getResults(): ResultType<T>[R] {
if (this.resultType === "dict") {
return this.results;
} else {
return Object.keys(this.results).
map(k => ({ k: k, v: this.results[k] }));
}
}
addResult(k: string, v: T) {
this.results[k] = v;
return this;
}
someMethod() {
return this;
}
wantArray(): FluentThing<T, "array"> {
return Object.assign(
new FluentThing(), this, { resultType: "array" }
) as FluentThing<T, "array">;
};
wantDict(): FluentThing<T, "dict"> {
return Object.assign(
new FluentThing(), this, { resultType: "dict" }
) as FluentThing<T, "dict">;
}
}
属性是字符串resultType
还是"array"
之一,它是"dict"
并且无意更改。当您调用readonly
或wantArray()
时,将创建一个新的wantDict()
对象,其数据与当前的对象相同,但指定的FluentThing
属性除外。另请注意resultType
方法返回getResults()
的方式,如果ResultType<T>[R]
为Dictionary<T>
则为R
,如果"dict"
为EntryArray<T>
则为R
"array"
。
让我们使用它:
const ft = new FluentThing<number>().addResult("a", 3).
addResult("b", 4).someMethod();
const dict = ft.wantDict().someMethod().getResults();
// dict known to be Dictionary<number> at compile time,
// {a: 3, b: 4} at runtime
const arr = ft.wantArray().someMethod().someMethod().getResults();
// arr known to be EntryArray<number> at compile time,
// [{k: "a", v: 3}, {k: "b", v: 4}] at runtime.
这一切都有效,消费者可以避免不必要的检查或断言。耶!
此时请注意返回新对象而不是改变现有对象的重要性。如果您确实改变了当前对象的resultType
并在调用this
或wantArray()
时返回wantDict()
...那么您将面临消费者会做类似事情的风险以下内容:
const wantArray = ft.wantArray();
const wantDict = ft.wantDict();
const shouldBeArray = wantArray.getResults();
FluentThing
的合同声明shouldBeArray
将是一个数组,但如果您只有一个FluentThing
对象,并且只要resultType
变异wantXXX()
被调用,它不会被调用,因为wantDict()
之后调用wantArray()
。只有您知道您的消费者是否可能保留您的流畅界面的中间值并尝试重复使用它们。如果没有,那么即使这个问题,也许突变对你有用。
另请注意,我不确定为什么要让消费者在实际获得结果之前指定他们希望结果作为数组或字典。从消费者的角度来看,从类型系统的角度来看,如果这个决定被推迟到数据实际返回给消费者之前,它会更直接:
type Dictionary<T> = { [k: string]: T }
type EntryArray<T> = { k: string, v: T }[];
class FluentThing<T> {
results: Dictionary<T> = {};
getResultsAsDictionary(): Dictionary<T> {
return this.results;
}
getResultsAsArray(): EntryArray<T> {
const resultsDict = this.getResultsAsDictionary();
return Object.keys(resultsDict).map(k => ({ k: k, v: resultsDict[k] }));
}
addResult(k: string, v: T) {
this.results[k] = v;
return this;
}
someMethod() {
return this;
}
}
const ft = new FluentThing<number>().addResult("a", 3).
addResult("b", 4).someMethod();
const dict = ft.someMethod().getResultsAsDictionary();
const arr = ft.someMethod().someMethod().getResultsAsArray();
除了一切都更简单之外,这几乎与之前的版本完全相同。这样的事情可能更合适,除非你的用例要求消费者提前指定所需的结果格式,因为某些原因我不知道。
无论如何,希望有所帮助。祝你好运!
答案 1 :(得分:0)
Typescript具有类型联合,也可以应用于方法。您可以使用以下概念使您的函数返回用户可能要求的两种类型之一。
class Foo {
test1: string = ' test value';
test2: number = 2;
bar(testArg: string | number): string | number {
return testArg;
};
}
let foo = new Foo();
let output1 = foo.bar(foo.test1);
let output2 = foo.bar(foo.test2);
console.log(output1); //test value
console.log(output2); //2