Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What AsyncGenerator TypeScript type yields a Promise?

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?

like image 630
Dan Dascalescu Avatar asked Mar 14 '20 10:03

Dan Dascalescu


2 Answers

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);
})();
like image 69
Aleksey L. Avatar answered Sep 21 '22 19:09

Aleksey L.


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)

like image 35
ericP Avatar answered Sep 20 '22 19:09

ericP