Я хочу инкапсулировать следующую логику во вспомогательную функцию:
if (!response.ok) {
throw Error(await response.text());
}
чтобы оно могло читаться как:
await myAssert(response.ok, async () => await response.text());
Но я не могу понять правильный синтаксис TypeScript. Если я сделаю это:
async function myAssert(
precondition: boolean,
messageMaker: () => Promise<string | Error>
): asserts precondition
{
const message = precondition ? "-unused-" : await messageMaker();
assert(precondition, message);
}
тогда компилятор TypeScript жалуется:
Тип возвращаемого значения асинхронной функции или метода должен быть глобальным типом Promise.
Но если я сделаю это:
async function myAssert(
precondition: boolean,
messageMaker: () => Promise<string | Error>
): Promise<void>
{
const message = precondition ? "-unused-" : await messageMaker();
assert(precondition, message);
}
тогда в вызывающем коде компилятор TypeScript не понимает, что response.ok
всегда должно быть истинным после ожидания утверждения.
Есть ли способ использовать оба варианта в TypeScript?
В настоящее время в TypeScript невозможно, чтобы функция утверждения была асинхронной . Для этого есть открытый запрос на добавление функции по адресу microsoft/TypeScript#37681, но на данный момент это не поддерживается.
Лучшее, что вы можете сделать, — это провести рефакторинг так, чтобы функция async
возвращала все интересующее вас состояние. Но это окажется путаницей, если вы хотите, чтобы это было общим. Вам может сойти с рук передача защитной функции пользовательского типа и утверждение ее результатов:
async function myAssert<T, U extends T>(
state: T & (Exclude<T, U> | U),
predicate: (t: T) => t is U,
msgMkr: (t: Exclude<T, U>) => Promise<string | Error>
): Promise<U> {
if (predicate(state)) {
return state;
} else {
const msg = await msgMkr(state);
throw (typeof msg === "string") ? new Error(msg) : msg;
}
}
что в настоящее время является болезненным в TypeScript 5.4, но в TypeScript 5.5 мы можем ожидать, что microsoft/TypeScript#57465 поможет вывести функции защиты типа. Итак, мы могли бы представить себе что-то вроде этого
type Resp =
{ success: true, data: string } |
{ success: false, error(): Promise<string> };
async function foo(r: Resp) {
if (!r.success) { throw new Error(await r.error()) }
console.info(r.data.toUpperCase());
}
foo({ success: true, data: "abc" }); // ABC
foo({
success: false, error() { return Promise.resolve("bad") }
}).catch(e => console.info(e)); // bad
и обернув его myAssert()
следующим образом:
async function fooWrapped(
r: Resp
) {
const goodR = await myAssert(r, r => r.success, r => r.error());
// inferred as type guard -----^^^^^^^^^^^^^^
console.info(goodR.data.toUpperCase());
}
fooWrapped({ success: true, data: "abc" }); // ABC
fooWrapped({
success: false, error() { return Promise.resolve("bad") }
}).catch(e => console.info(e)); // bad
Но, похоже, оно того не стоит.
Ссылка на код детской площадки