Можно ли сделать утверждение значения TypeScript асинхронным?

Я хочу инкапсулировать следующую логику во вспомогательную функцию:

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?


1
61
1

Ответ:

Решено

В настоящее время в 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

Но, похоже, оно того не стоит.

Ссылка на код детской площадки