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?
It will be AsyncGenerator<number, never, void>
:
number
- next
resultnever
returnsvoid
- 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);
})();
Because many many folks looking here will need some exit condition, I tweaked the question and Aleksey L.'s answer to:
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With