Как перегрузить функцию в TypeScript необязательными параметрами? - PullRequest
3 голосов
/ 22 апреля 2020

У меня есть следующий код, который частично преобразован в TypeScript (из JavaScript).

В основном, если существует параметр callback, я всегда хочу, чтобы сама функция возвращала void. В противном случае тип возврата Promise<object>. С необязательным параметром (settings) перед этим (так что технически параметр callback может быть передан как параметр settings, который первые несколько строк функции обрабатывают с помощью регистра).

В целях обратной совместимости (и для сохранения кода DRY) я не хочу создавать другую функцию с именем savePromise или saveCallback и отделять ее. Я пытаюсь понять, как заставить TypeScript быть достаточно умным, чтобы как-то понять эту логику c.

type CallbackType<T, E> = (response: T | null, error?: E) => void;

class User {
    save(data: string, settings?: object, callback?: CallbackType<object, string>): Promise<object> | void {
        if (typeof settings === "function") {
            callback = settings;
            settings = undefined;
        }

        if (callback) {
            setTimeout(() => {
                callback({"id": 1, "settings": settings});
            }, 1000);
        } else {
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve({"id": 1, "settings": settings});
                }, 1000);
            });
        }
    }
}

const a = new User().save("Hello World"); // Should be type Promise<object>, should eventually resolve to {"id": 1, "settings": undefined}
const b = new User().save("Hello World", (obj) => {
    console.log(obj); // {"id": 1, "settings": undefined}
}); // Should be type void
const c = new User().save("Hello World", {"log": true}); // Should be type Promise<object>, should eventually resolve to {"id": 1, "settings": {"log": true}}
const d = new User().save("Hello World", {"log": true}, (obj) => {
    console.log(obj); // {"id": 1, "settings": {"log": true}}
}); // Should be type void

Я почти уверен, что файл типа, к которому я стремлюсь, будет чем-то линии следующие. Не уверен, что я здесь прав, но

save(data: string, settings?: object): Promise<object>;
save(data: string, callback: CallbackType<object, string>): void;
save(data: string, settings: object, callback: CallbackType<object, string>): void;

Кажется, что параметр callback, передаваемый как вариант использования параметра settings, можно обработать, выполнив что-то вроде:

save(data: string, settings?: object | CallbackType<object, string>, callback?: CallbackType<object, string>): Promise<object> | void

Но это очень грязно, и из моего опыта не похоже, что TypeScript достаточно умен, чтобы понять, что settings всегда будет необязательным объектом после тех первых 4 строк кода в функции. Что означает, что при вызове callback вы должны набрать cast, что опять-таки кажется очень грязным.

Как мне добиться этого с помощью TypeScript?

1 Ответ

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

TL; DR

Вот решение (с некоторым рефакторингом):

type CallbackType<T, E> = (response: T | null, error?: E) => void;
interface ISettings {
  log?: boolean;
}
interface ISaveResult {
  id: number;
  settings: ISettings | undefined;
}

class User {
  save(data: string): Promise<ISaveResult>;
  save(data: string, settings: ISettings): Promise<ISaveResult>;
  save(data: string, callback: CallbackType<ISaveResult, string>): void;
  save(data: string, settings: ISettings, callback: CallbackType<ISaveResult, string>): void;
  save(data: string, settings?: ISettings | CallbackType<ISaveResult, string>, callback?: CallbackType<ISaveResult, string>): Promise<ISaveResult> | void {
    if (typeof settings !== "object" && typeof settings !== "undefined") {
      callback = settings;
      settings = undefined;
    }

    const localSettings = settings; // required for closure compatibility
    if (callback) {
      const localCallback = callback; // required for closure compatibility
      setTimeout(() => {
        localCallback({ id: 1, "settings": localSettings });
      }, 1000);
    } else {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({ id: 1, "settings": localSettings });
        }, 1000);
      });
    }
  }
}

const a = new User().save("Hello World"); // User.save(data: string): Promise<ISaveResult>

const b = new User().save("Hello World", obj => { 
  console.log(obj); // obj: ISaveResult | null
}); // User.save(data: string, callback: CallbackType<ISaveResult, string>): void

const c = new User().save("Hello World", { "log": true }); // User.save(data: string, settings: ISettings): Promise<ISaveResult>

const d = new User().save("Hello World", { "log": true }, (obj) => {
  console.log(obj); // obj: ISaveResult | null
}); // User.save(data: string, settings: ISettings, callback: CallbackType<ISaveResult, string>): void

см. кодовая панорама

Объяснение

a Function также является Object, поэтому TypeScript не смог неявно различить guish между ними. Создавая указанный интерфейс c ISettings, вы позволяете TypeScript различать guish между объектом настроек и функцией обратного вызова.

Самый простой способ увидеть это - посмотреть на ошибки TypeScript выводит и типы переменных в процессе выполнения кода, например ( ваш код ):

  • При наведении курсора на settings в if состоянии:

    enter image description here

  • При наведении на settings внутри if блока:

    enter image description here

  • callback ошибка назначения:

    Тип «Функция» не может быть назначен типу «CallbackType». Тип «Функция» не обеспечивает совпадения для подписи »(ответ: объект | ноль, ошибка ?: строка | неопределенный): void '. (2322)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...