The goal of this proposal is to better facilitate the use of for loop syntax when working with streams of character data, which when expressed as a purely async iterables tend to have poor performance given that most of the time the next character in the iterable is available synchronously, while only occasionally does another chunk of data need to be read asynchronously, e.g. from disk. Forcing these synchronous operations to be asynchronous incurs unnecessary wall clock time cost from calls to queueMicrotask
(or setImmediate
or whatever). In fact that cost is incurred twice: once to await step
, and once to await step.value
.
The proposal is to create a new type of iterator and a new syntax for consuming it.
The new type is a syncAndAsync
iterator. The interface for it would be:
{
next<T>(): { done: boolean, value: T } | Promise<{done: boolean, value:T }>;
}
The strawman expects an iterable which can produce such an iterator to be returned from a Symbol.for('syncAndAsyncIterator')
, though ultimately having Symbol.syncAndAsyncIterator
as a WKS would seem most apt.
Iteration over a syncAndAsync
iterator is facilitated with an extension of the for loop syntax:
for await? (const value of syncAndAsyncIteratable) {
}
for await?
is expressed in the AST as { async: true, sync: true }
where for await
would be { async: true, sync: false }
and a for
would be { async: false, sync: true }
.
Creation of a syncAndAsync
iterator could be done with async generator functions. It is my belief that it would not be necessary to define any new syntax for generator functions as their internal implementation could be changed to to yield iterator steps synchronously when no awaiting had been necessary since the previous call to yield
. The current API would continue to be served by implementing [Symbol.asyncIterator]()
as thin wrapper around [Symbol.syncAndAsyncIterator]()
.