Как правильно набрать эту функцию `makeCancellable` и заставить Flowtype перестать жаловаться? - PullRequest
0 голосов
/ 16 декабря 2018

У меня есть следующая функция для отмены обещаний.Как вы можете видеть, я определил интерфейс CancellablePromise, который расширяет исходный Promise, и у меня есть некоторые функции проверки типов, помеченные %checks, чтобы проверить, является ли значение Promise или это уже отменяемое обещание.Основная функция makeCancellable должна принимать Promise в качестве аргумента и возвращать CancellablePromise.Все работает , как и ожидалось, но мне пришлось использовать аннотации $FlowFixMe в 3 местах, потому что Flow жаловался, что бы я ни пытался.(Прочтите комментарии в приведенном ниже коде, чтобы узнать, на что жалуется Flow).

Что меня больше всего беспокоит, так это самая первая проверка isCancellable, в которой я проверяю, является ли обещание уже CancellablePromise.Как ни странно, если я отмечу аргумент originalPromise функции как any, как CancellablePromise<*> | number или как угодно, система проверки типов увидит, что функция isCancellable является защитой типа, и перестанет жаловаться.Когда аргумент originalPromise набирается так, как в примере кода ниже, каким-то образом Flow игнорирует тот факт, что isCancellable является правильно типизированным защитником типа, и начинает жаловаться так же, как если бы защиты %checks не было.

Буду признателен за любую помощь.У меня не было таких проблем, когда я использовал TypeScript некоторое время назад - там прекрасно работали охрана и типизация, но почему-то не получается заставить Flow сотрудничать.

interface CancellablePromise<T> extends Promise<T> {
    cancel: Function
};

function isThenable(value: any): boolean %checks {
    return typeof value === 'object' && 'then' in value;
}

function isCancellable(promise: any): boolean %checks {
    return isThenable(promise) && 'cancel' in promise;
}

function makeCancellable(originalPromise: CancellablePromise<*> | Promise<*>): CancellablePromise<*> {
    if (isCancellable(originalPromise)) {
        // $FlowFixMe
        return originalPromise; // <- complains here because Promise type does not have `cancel` method, ignores the isCancellable %checks
    }

    let resolvePromise;
    let rejectPromise;
    let cancelled = false;

    const cancellable = new Promise((resolve, reject) => {
        resolvePromise = resolve;
        rejectPromise = reject;
    });

    // $FlowFixMe
    // Complains here because Promise cannot have 'cancel' method
    // Typecasting with (cancellable: CancellablePromise<*> does not work
    cancellable.cancel = () => {
        cancelled = true;
    }

    originalPromise.then(result => {
        cancelled || resolvePromise(result);
    }).catch(reason => {
        cancelled || rejectPromise(reason);
    });

    // $FlowFixMe
    // Complains here because Promise lacks 'cancel' method because adding the method and typecasting does not work
    return cancellable;
};

1 Ответ

0 голосов
/ 19 декабря 2018

Проблема в том, что Promise является классом, а классы в Flow являются номинальными типами (в отличие от Typescript, где они являются структурными типами, такими как интерфейсы).В Flow единственный способ добавить поля к типу, который является классом, - это создать подкласс.Например,

class CancellablePromise<T> extends Promise<T> {
    cancelled = false;
    cancel() { this.cancelled = true; }
};

Вы можете дополнительно определить интерфейс с подписями then и cancel.Проблема с интерфейсом, расширяющим класс, заключается в том, что любое значение типа интерфейса должно быть экземпляром Promise или подклассом Promise.Это снижает полезность интерфейса над подклассом.В Typescript класс может использоваться как интерфейс, но в этом отношении Flow работает иначе.

Форму подкласса CancellablePromise можно создать непосредственно:

const cancellable = new CancellablePromise((resolve, reject) => {
    resolvePromise = resolve;
    rejectPromise = reject;
});
...