SE-0298: Async/Await: Sequences

+0.9 - Async sequences would be a very useful addition to Swift. My only concerns are related to areas where the design of this feature diverges from other similar features in mainstream languages.

cancel isn't async

I'm familiar with both .Net await foreach / IAsyncEnumerable and JavaScript's for-await-of / Symbol.asyncIterator and this proposal seems to follow the same structure with one exception: the design of func cancel(). The .Net equivalent is DisposeAsync() and the JS equivalent is return(). These return (Value)Task/Promise respectively, meaning that they are asynchronous operations. I'm not saying the design in Swift needs to be identical, but I'd like to understand why you think Swift is unlikely to need asynchronous cleanup for early exit of a for await?

An example use case would be if the AsyncSequence represents the query results from a database, you might need network access to close the cursor, which would imply making it async.

If/when Swift supports generators for implementing (Async)IteratorProtocol, this would correspond to supporting await within defer blocks in the generator. I note that this isn't currently supported, so a synchronous func cancel() is at least consistent with that.

How to cancel an iteration from outside the loop

I also had questions about how this interacts with cancellation of the iteration from outside the loop / AsyncSequence functions. For example, if a user interface triggers an operation that maps over an AsyncSequence backed by a paged web service API. How could this operation be cancelled? I think this would be supported by something like

struct API {
  func query() -> AsyncSequence<APIResult>
}
let handle = Task.runDetached { self.results = api.query().map { ... } }
...
handle.cancel()

Is this correct?

Naming of func cancel()

Would this be better named something else? I found the name of this confusing when I was thinking about the above case of cancelling from outside the loop. It seems like this is actually doing some cleanup after cancellation. .Net has Dispose(Async) to standardise this. JS calls this return. If it's not going to be part of deinit, would a name like close, finalise, cleanup be clearer?

Yes and yes.

I've used asyncIterator + IxJS in JavaScript for processing CSV files that were too big to fit in memory and they made the computation much more comprehensible than they would otherwise have been using Node streams.

Took part in the Pitch thread, read the proposal several times and compared the structure to similar features in .Net + JavaScript.

3 Likes