Как объявить общее обещание в Typescript, так что когда тип Generic "<void>", один из его методов не будет принимать параметр?

В Typescript я хочу иметь возможность определять тип Promise таким образом, чтобы я мог это сделать:

//This works today:
new Promise<number>((resolve)=>{
   //Cool
   resolve(5);
   //Error, because I didn't pass a number:
   resolve();
}

//This is what I want to do also:
new Promise<void>((resolve)=>{
   //Error, because I passed a value:
   resolve(5);
   //Cool, because I declared the promise to be of type void, so resolve doesn't take a value:
   resolve();
}

В файлах определения обещаний, которые я видел, все заявляют, что метод "решения" обещания должен принимать значение. Здесь - это недавний пример замечательного проекта DefinitelyTyped:

declare class Promise<R> implements Thenable<R> {
    constructor(callback: (resolve : (result: R) => void, reject: (error: any) => void) => void); 
///...
}

`` `

В основном это говорит: "Обратный вызов разрешения должен быть передан значение типа R." Это прекрасно для обещания вроде new Promise<number>. Typescript проверит, что мы вызываем решение со значением типа number.

Однако, если я хочу обещание, которое не имеет значения, поэтому я хочу, чтобы иметь возможность вызвать resolve() без передачи значения? Я могу объявить свое обещание следующим образом: new Promise<void> Но тогда я все еще вынужден назвать решение и передать какую-то ценность. Я могу вызвать resolve(undefined), но это немного странно.

Кажется, нет способа правильно зафиксировать это понятие в Typescript: "Если этот общий тип имеет тип" void ", тогда не ожидайте параметра для этой функции."

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

Ответ 1

Возможно, я придумал обходное решение, которым я доволен. В случае, когда я хочу иметь Promise<void>, который гарантирует, что обратный вызов resolve не принимает параметр, вместо того, чтобы заставить метод разрешения всегда принимать необязательный параметр, я могу определить новый класс следующим образом:

export class VoidPromise extends RSVP.Promise<void>{
    //Note that resolve does not take a parameter here:
    constructor(callback:(resolve:() => void, reject:(error:any) => void) => void){
        super(callback);
    }
}

И в этом случае я могу использовать его так:

    public static testVoidPromise() : VoidPromise{
        return new VoidPromise((resolve, reject)=>{

            setTimeout(1000, ()=>{
                if (Math.random() < 0.5){
                    //Note that resolve() does NOT take a parameter
                    resolve();
                }else{
                    reject(new Error("Something went wrong"));
                }
            })
        });
    }

Конечно, разработчикам придется использовать VoidPromise вместо просто "Promise", но предполагаемый эффект достигается без ложной маркировки параметра разрешения как необязательного.

В моем сценарии приведенный выше код удовлетворяет моим ожиданиям больше, чем маркировка всех методов resolve как имеющих необязательный результат. Пометить все результаты как необязательно, опасно в случае 99%. Если он всегда является необязательным, я могу объявить Promise<number>, вызвать resolve() без результата и получить результат undefined для моего обещания. В таком случае я должен был отклонить это обещание. Я не верю, что ожидается, что параметр метода разрешения действительно является необязательным. (Источник: https://github.com/domenic/promises-unwrapping#the-promise-constructor)

Ответ 2

Это работает!
определения обещаний были обновлены для поддержки такого варианта использования.

Исходный пример слишком длинный для публикации. Но просто скопируйте/вставьте новые определения и код Promise в редактор, и вы увидите, что ваш пример теперь работает.

Ответ 3

По тому, как вы хотите использовать интерфейс Promise, кажется, что вы хотите иногда передавать значение, а иногда вы хотите передать никакое значение, так что для чего нужны дополнительные параметры. Если решение с результатом "undefined" неприемлемо (IMO это не плохая идея, это говорит о том, что происходит в коде - результат обещания udefined) Я могу предложить решение, которое, похоже, является тем, что вы ищете:

Я бы определил "контракт" для обещания, скажем:

export interface IMyPromiseResult {
    result: number;
}

и использовать его с обещанием:

new Promise<IMyPromiseResult>((resolve)=>{

   resolve(<IMyPromiseResult>{ result: 5 });

   resolve(<IMyPromiseResult>{ });
}

Хотя этот подход немного сложнее, он открывает некоторые интересные варианты... С другой стороны, текущий конструктор DefinitelyTyped Promise определяется как:

constructor(callback: (resolve: (result?: R) => void, reject: (error: any) => void) => void);

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