What AsyncGenerator TypeScript type yields a Promise?
Asked Answered
A

2

10

I'm trying to assign a return type to the function below:

async function *sleepyNumbers() {  // what TypeScript type is this?
  let n = 0;
  while (true) {
    yield new Promise(resolve => resolve(n++));
    await new Promise(resolve => setTimeout(resolve, 500));
  }
}

(async () => {
  for await (const i of sleepyNumbers())
    console.log(i);
})();

The generator is yielding a Promise that resolves to a number. Setting the type to Promise<number> fails with this error message:

TS2739: Type 'AsyncGenerator' is missing the following properties from type 'Promise': then, catch, [Symbol.toStringTag], finally

Iterable resulted in a similar error.

I can set the type to AsyncGenerator but that's not specific enough. What is the proper TypeScript syntax for the return type of this function?

Ardie answered 14/3, 2020 at 10:35 Comment(0)
B
22

It will be AsyncGenerator<number, never, void>:

number - next result
never returns
void - next doesn't get any parameter

You'll also need to explicitly type a promise resolve:

yield new Promise<number>(resolve => resolve(n++));

All together:

async function *sleepyNumbers(): AsyncGenerator<number, never, void> {
    let n = 0;
    while (true) {
        yield new Promise<number>(resolve => resolve(n++));
        await new Promise(resolve => setTimeout(resolve, 500));
    }
}

(async () => {
    for await (const i of sleepyNumbers())
        console.log(i);
})();
Bogus answered 14/3, 2020 at 12:28 Comment(4)
let n = 0; yield n++; while (true) yield new Promise(resolve => setTimeout(resolve, 500, n++));Snail
You might be able to shorten the yield a little bit by writing yield Promise.resolve(n++).Folk
@Folk thanks, but this is not relevant to original question (where the code is taken from)Bogus
@AlekseyL. Ok. It's just a tip.Folk
P
13

Because many many folks looking here will need some exit condition, I tweaked the question and Aleksey L.'s answer to:

  1. make it stop after 4 iterations:
  2. fix the resulting error TS2534: A function returning 'never' cannot have a reachable end point. (tested using the lib "es2018.asyncgenerator").
async function* sleepyNumbers(count: number): AsyncGenerator<number, void, void> {
  let n = 0;
  while (n < count) {
    yield new Promise<number>(resolve => resolve(n++));
    await new Promise(resolve => setTimeout(resolve, 250));
  }
}

(async () => {
  for await (const i of sleepyNumbers(4))
    console.log(i);
})();

#2 required making the 2nd template arg to AsyncGenerator be void because the generator function (the function*) falls off the end without a return and the caller gets back:

{ value: undefined, done: true }

Tweaking the generator to pass a final value when done, we see the use of the 2nd template parameter:

async function* sleepyNumbers(count: number): AsyncGenerator<number, string, void> {
  let n = 0;
  while (n < count) {
    yield new Promise<number>(resolve => resolve(n++));
    await new Promise(resolve => setTimeout(resolve, 250));
  }
  return 'some string';
}

(async () => {
  const it = sleepyNumbers(4);
  let res;
  for (res = await it.next(); !res.done; res = await it.next())
    console.log(res);
  console.log('Finished with:', res);
  console.log('past end 1:', await it.next());
  console.log('past end 2:', await it.next());
})();

, producing the output:

{ value: 0, done: false }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
Finished with: { value: 'some string', done: true }
past end 1: { value: undefined, done: true }
past end 2: { value: undefined, done: true }

Apparently, banging on an iterator after the generator has completed will always come back with value: undefined.

tldr; (as if this wasn't already in tldr-land), we've been playing with the TReturn parameter of the AsyncGenerator template:

interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
    // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
    next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
    return(value: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<T, TReturn>>;
    throw(e: any): Promise<IteratorResult<T, TReturn>>;
    [Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
}

(per node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts), which maps to TReturn in

interface IteratorYieldResult<TYield> {
    done?: false;
    value: TYield;
}

interface IteratorReturnResult<TReturn> {
    done: true;
    value: TReturn;
}

type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;

(per lib.es2015.iterable.d.ts)

Proem answered 24/1, 2021 at 6:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.