Основная проблема, с которой вы сталкиваетесь, - это, возможно, удивительная совместимость типов таких значений, как x => x + 1
и i => i * 2
с и SyncFunc<number, number>
и AsyncFunc<number, number>
:
const sf: SyncFunc<number, number> = x => x + 1; // okay
const af: AsyncFunc<number, number> = x => x + 1; // also okay... what?!
Это работает, как задумано, но смущает достаточное количество людей, что является частью FAQ по TypeScript . Я адаптирую информацию, содержащуюся в этом вопросе:
В: Как x => x + 1
можно присвоить AsyncFunc
, когда первый принимает один параметр, а второму требуется два параметра? Каким образом функция одного параметра может быть назначена функции двух параметров?
A: x => x + 1
является допустимым назначением для AsyncFunc
, поскольку оно может безопасно игнорировать дополнительные параметры. Во время выполнения (x => x + 1)(10, "randomExtraParam")
работает. Если сделать это ошибкой во время компиляции, это приведет к аннулированию многих традиционных методов, таких как Array.forEach()
или Array.map()
, реализация которых передает несколько параметров обратному вызову, но часто используется с обратными вызовами одного параметра. Вы можете прочитать запись FAQ, связанную выше, чтобы узнать, почему они считают это лучшим поведением здесь.
Q: Как можно x => x + 1
быть назначенным на AsyncFunc
, когда первый возвращает number
(при условии x
выводится как number
), а последний возвращает void
? Как функция, возвращающая значение, может быть присвоена той, которая этого не делает?
A: Тип возврата void
означает, что вызывающая сторона не может ожидать возврата значение, но это не значит, что точно не будет. Это просто означает, что вызывающая сторона должна игнорировать любое возвращаемое значение. Если вы игнорируете какое-либо возвращаемое значение, которое я вам даю, тогда не должно иметь значения, что я return 1
вместо return undefined
. Если сделать это ошибкой, это приведет к аннулированию многих традиционных функций callback-acceptpting, которые не обращаются к возвращаемому значению обратного вызова, например Array.forEach()
. Некоторые обратные вызовы, такие как a => arr.push(a)
, имеют как побочные эффекты, так и возвращаемые значения, а запрещение возвращаемых значений затруднит их использование обычными способами.
В свете этого поведение понятно; компилятор не может надежно различить guish между AsyncFunc<number, number>
и SyncFunc<number, number>
. Чтобы это произошло, вы должны изменить типы, чтобы они были несовместимы. Один из способов сделать это - сделать тип возвращаемого значения AsyncFunc
отличным от void
, например undefined
:
type Resolve<T> = SyncFunc<T, undefined>;
type AsyncFunc<I, O> = (i: I, cb: Resolve<O>) => undefined;
Они похожи в том, что функция без return someValue
оператор в конечном итоге вернет undefined
, но компилятор теперь будет недоволен, если вы вернете 1
вместо undefined
в AsyncFunc
.
Это все еще не решает проблему различения функций по количеству параметров. Если вы просто напишите сигнатуру вызова map()
как <I, O>(m: Func<I, O>): Pin<O, I>
, то у компилятора технически достаточно информации, чтобы увидеть, что x => x + 1
должен быть SyncFunc
, но, очевидно, слишком много, чтобы он мог сделать вывод сразу: он должен выведите как параметр типа I
, так и тип x
, но они зависят друг от друга, и он сдается. Иногда вы можете заставить его работать, но это не всегда стоит усилий. См. microsoft / TypeScript # 30134 для получения информации о проблеме и обсуждении, касающихся ограничений текущего алгоритма вывода типов и возможностей его улучшения.
На данный момент, однако, у вас уже есть обходной путь ведет себя так, как вы хотите: используйте перегрузки , чтобы сначала компилятор приложил серьезные усилия для интерпретации x => x + 1
как AsyncFunc
, произвел сбой (из-за несовместимого типа возвращаемого значения), а затем попытался SyncFunc
и добиться успеха. Это имеет ту же проблему, что и раньше, о I
и x
, но тип SyncFunc
достаточно прост для работы (по сравнению с Func
).
Это означает, что для вас работает следующее:
function map<I, O>(m: AsyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: Func<I, O>): Pin<O, I> {
return new Pin<O, I>();
}
src<number>().to(map((i, c: Resolve<number>) => c(i * 2))).to(map(x => x + 1));
src<number>().to(map(i => i * 2)).to(map(x => x + 1));
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код