8
votes

I expect type void[] to be compatible with type void. Specifically, when using Promise.all.

class Foo {
  delete(): Promise<void> {
    // do delete
    return Promise.resolve();
  }
  deleteMany(list: Foo[]): Promise<void> {
    return Promise.all(list.map((x) => x.delete()));
  }

typescript error:

'Type 'Promise<void[]>' is not assignable to type 'Promise<void>'. Type 'void[]' is not assignable to type 'void'.'

I can solve this two ways that I know of:

  1. Mark deleteMany as returning Promise<void[]>.
  2. Promise chain to Promise.all, to return a resolved promise. e.g.

    return Promise.all(list.map((x) => x.delete())).then(() => Promise.resolve());
    

The second one is worse, since that bit of code executes in JavaScript, but the first one is confusing to developers. Does Typescript have poor support for Promise.all or is it omitted from their documentation? Anyone find a better solution?

2
Why not just mark deleteMany as returning Promise<void[]>? That's not poor support, that's what a Promise.all call returns...Heretic Monkey
As @MikeMcCaughan suggests.. I agree.. Promise.all returns a Promise of array of the type, which in this case it is voidradix
3rd option: Just leave the return type off entirely. The compiler will infer it and raise an error if it is used incorrectly. Go with 1 or 3, 2 does not make sense.Aluan Haddad
From the MDN docs: "If all of the passed-in promises fulfill, Promise.all is fulfilled with an array of the values from the passed-in promises, in the same order as defined in the iterable." This is the expected result of Promise.all -- a promise that resolves to an array containing the fulfilled values which in this case will all be void. It would be confusing to developers to type it with anything BUT Promise<void[]>sbking
@MikeMcCaughan Because consumers shouldn't care about implementation detail. I like Aluan's suggestion the best. sbking You're absolutely right, however, I find arrays filled with void types terribly unhelpful.Jerroyd M.

2 Answers

6
votes

The 1 (Mark deleteMany as returning Promise<void[]>) is absolutely ok at least for me.

However, you can use async/await if you really want to return Promise<void>:

class Foo {
  delete(): Promise<void> {
    // do delete
    return Promise.resolve();
  }
  async deleteMany(list: Foo[]): Promise<void> {
    await Promise.all(list.map((x) => x.delete()));
  }

The function will behave exactly the same way.

3
votes

void is incompatible with any other type except null and undefined by design, so it's incompatible with void[].

You could use Promise<{}> as result type of deleteMany, that is, promise resolving to empty object. It works, because empty object type has no properties (so it can not meaningfully be used in any way), and, because it has no properties (again), it could be assigned almost anything, including void[]:

class Foo {
    delete(): Promise<void> {
        // do delete
        return Promise.resolve();
    }
    deleteMany(list: Foo[]): Promise<{}> {
        return Promise.all(list.map((x) => x.delete()));
    }
}