Функция фильтра Rx JS не сужает тип, если только непосредственно не задан typeguard, это единственный параметр - PullRequest
0 голосов
/ 23 апреля 2020

Я работал над службой аутентификации, которая использует субъект поведения rx js для хранения последнего найденного объекта аутентификации и запускает повторное извлечение, если срок его действия истек (или еще не был получен).

Мой вопрос касается проверки типов TypeScript. Я написал typeguard isNotUndefined, который утверждает - ну, именно то, что вы ожидаете.

export function isNotUndefined<T>(input: T | undefined): input is T {
  return input !== undefined;
}

Мне уже приходилось писать вышеупомянутый typeguard вместо того, чтобы полагаться на auth !== undefined. Теперь я не могу понять, почему в канале в authGetter$ в приведенном ниже коде тип значения в канале не уменьшается до Auth после первого фильтра. Вместо этого тип по-прежнему Auth | undefined, и ему требуется второй фильтр с защитой типа, чтобы сузить тип до Auth.

Итак, зачем мне нужен второй фильтр сузить тип до Auth? Кроме того, поскольку я пишу код самостоятельно, и никто его не просматривает, я был бы очень признателен всем, кто указал на «запахи кода», которые они распознают (с предложениями о том, что делать вместо этого).

export default class AuthService {
  private static lastAuth$ = new BehaviorSubject<Auth | undefined>(undefined);

  private static authGetter$ = AuthService.lastAuth$.pipe(
    filter(auth => {
      if (isNotUndefined(auth) && auth.expiry > new Date()) {
        return true ; // identical resulting type with "return isNotUndefined(auth);"
      } else {
        // retry if auth doesn't exist or is expired
        AuthService.authorise().then(newAuth =>
          AuthService.lastAuth$.next(newAuth)
        );
        return false;
      }
    }),
    tap(v => {}), // typechecker says "(parameter) v: Auth | undefined"
    filter(isNotUndefined),
    tap(v => {}) // typechecker says "(parameter) v: Auth"
  );

  static getAuth$(): Observable<Auth> {
    return this.authGetter$.pipe(first());
  }

  private static async authorise(): Promise<Auth> {
    // auth code goes here (irrelevant for this question)...
    // typecast dummy return to make typechecker happy
    return Promise.resolve(<Auth>{});
  }
}

Я прилагаю фотография моего кода с хорошей подсветкой синтаксиса для удобства просмотра:)

my code in nice syntax highlighting

1 Ответ

1 голос
/ 23 апреля 2020

Определяемые пользователем функции защиты типа , по крайней мере в настоящее время, строго определены пользователем. Они не определяются автоматически компилятором. Если вы хотите, чтобы возвращаемая функция boolean работала как защита типа с возвращаемым типом предиката типа, вам необходимо явно пометить ее следующим образом:

const doesNotPropagate = <T>(x: T | undefined) => isNotUndefined(x);
// const doesNotPropagate: <T>(x: T | undefined) => boolean

Функция doesNotPropagate() ведет себя так же, как isNotUndefined() во время выполнения, но компилятор больше не видит его как средство защиты типов, поэтому, если вы используете его в качестве фильтра, вы не исключите undefined в компиляторе.

Существует несколько проблем в GitHub об этом; в настоящее время открытые подписи охраняющего / пропускающего типа отслеживания ошибок: microsoft / TypeScript # 16069 (или, возможно, microsoft / TypeScript # 10734 ). Но, похоже, здесь нет большого движения, так что пока нам нужно просто разобраться с языком таким, какой он есть.


Вот игрушечный пример, с которым можно поиграть различные возможные способы решения этой проблемы, поскольку пример кода в вашем вопросе не представляет собой минимальный воспроизводимый пример , подходящий для перехода в автономную среду IDE. Вы должны иметь возможность адаптировать их к своему собственному коду.

Допустим, у нас есть значение o типа Observable<string | undefined>. Тогда это работает:

const a = o.pipe(filter(isNotUndefined)); // Observable<string>

, но это не по причине, указанной выше ... подписи охранника типа не распространяются:

const b = o.pipe(filter(x => isNotUndefined(x))); // Observable<string | undefined>

Мы можем восстановить охрану типа подпись и поведение, если мы вручную аннотируем функцию стрелки следующим образом:

const c = o.pipe(filter((x): x is string => isNotUndefined(x))); // Observable<string>;

Из этого вы можете сделать дополнительную фильтрацию logi c, если хотите:

const d = o.pipe(filter((x): x is string => isNotUndefined(x) && x.length > 3)); 
// Observable<string>;

Здесь фильтр проверяет что строка определена как и , что ее длина больше 3.

Обратите внимание, что это технически не очень корректный пользовательский тип защиты, так как они имеют тенденцию обрабатывать false результаты означают, что ввод сужается до исключить защищенный тип:

function badGuard(x: string | undefined): x is string {
  return x !== undefined && x.length > 3;
}
const x = Math.random() < 0.5 ? "a" : undefined;
if (!badGuard(x)) {
  x; // narrowed to undefined, but could well be string here, oops
}

Здесь, если badGuard(x) возвращает true, вы знаете, что x равно string , Но если badGuard(x) возвращает false, вы не знаете, что x - это undefined ... но так думает компилятор.

Это правда, что в вашем код, который вы на самом деле не имеете дело с ситуацией, когда фильтр возвращает false (я полагаю, что последующие параметры канала просто не срабатывают?), так что вам не нужно особо беспокоиться об этом. Тем не менее, может быть лучше выполнить рефакторинг кода в один правильный тип защиты, за которым следует фильтр без защиты типа, который выполняет дополнительные операции логарифмирования c:

const e = o.pipe(filter(isNotUndefined), filter(x => x.length > 3)); // Observable<string>;

Это должно равняться тому же результату при время выполнения, но здесь первый фильтр корректно сужается от Observable<string | undefined> до Observable<string>, а второй фильтр сохраняет Observable<string>x в обратном вызове равен string) и выполняет дополнительную логику c что фильтры по длине.

И это дает дополнительное преимущество: не требуется аннотации типа, поскольку вы нигде не пытаетесь распространять сигнатуру защиты типа. Так что, вероятно, я рекомендую этот метод.


Хорошо, надеюсь, это поможет; удачи!

Stackblitz ссылка на код

...