11

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?

2 Answers 2

23

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);
})();
Sign up to request clarification or add additional context in comments.

4 Comments

let n = 0; yield n++; while (true) yield new Promise(resolve => setTimeout(resolve, 500, n++));
You might be able to shorten the yield a little bit by writing yield Promise.resolve(n++).
@tinnick thanks, but this is not relevant to original question (where the code is taken from)
@AlekseyL. Ok. It's just a tip.
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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.