Skip to content

Commit d94bfe6

Browse files
committed
feat: add SafeAsyncIter chaining
1 parent 313d6ba commit d94bfe6

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

codex/utils/safeasynciter.nim

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,28 @@ proc empty*[T](_: type SafeAsyncIter[T]): SafeAsyncIter[T] =
232232
true
233233

234234
SafeAsyncIter[T].new(genNext, isFinished)
235+
236+
proc chain*[T](iters: seq[SafeAsyncIter[T]]): SafeAsyncIter[T] =
237+
if iters.len == 0:
238+
return SafeAsyncIter[T].empty
239+
240+
var curIdx = 0
241+
242+
proc ensureNext(): void =
243+
while curIdx < iters.len and iters[curIdx].finished:
244+
inc(curIdx)
245+
246+
proc isFinished(): bool =
247+
curIdx == iters.len
248+
249+
proc genNext(): Future[?!T] {.async: (raises: [CancelledError]).} =
250+
let item = await iters[curIdx].next()
251+
ensureNext()
252+
return item
253+
254+
ensureNext()
255+
256+
return SafeAsyncIter[T].new(genNext, isFinished)
257+
258+
proc chain*[T](iters: varargs[SafeAsyncIter[T]]): SafeAsyncIter[T] =
259+
chain(iters.toSeq)

tests/codex/utils/testsafeasynciter.nim

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ asyncchecksuite "Test SafeAsyncIter":
373373
# Now, to make sure that this mechanism works, and to document its
374374
# cancellation semantics, this test shows that when the async predicate
375375
# function is cancelled, this cancellation has immediate effect, which means
376-
# that `next()` (or more precisely `getNext()` in `mapFilter` function), is
376+
# that `next()` (or more precisely `getNext()` in `mapFilter` function), is
377377
# interrupted immediately. If this is the case, the the iterator be interrupted
378378
# before `next()` returns this locally captured value from the previous
379379
# iteration and this is exactly the reason why at the end of the test
@@ -404,14 +404,29 @@ asyncchecksuite "Test SafeAsyncIter":
404404

405405
expect CancelledError:
406406
for fut in iter2:
407-
if i =? (await fut):
407+
without i =? (await fut), err:
408408
collected.add(i)
409-
else:
410-
fail()
411409

412410
check:
413411
# We expect only values "0" and "1" to be collected
414412
# and not value "2" that - although resolved and ready to be returned -
415413
# will not be returned because of the cancellation.
416414
collected == @["0", "1"]
417415
iter2.finished
416+
417+
test "should allow chaining":
418+
let
419+
iter1 = SafeAsyncIter[int].new(0 ..< 5)
420+
iter2 = SafeAsyncIter[int].new(5 ..< 10)
421+
iter3 = chain[int](iter1, SafeAsyncIter[int].empty, iter2)
422+
423+
var collected: seq[int]
424+
425+
for fut in iter3:
426+
without i =? (await fut), err:
427+
fail()
428+
collected.add(i)
429+
430+
check:
431+
iter3.finished
432+
collected == @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

0 commit comments

Comments
 (0)