Skip to content

Commit ebb50a0

Browse files
author
Thibault Wittemberg
committed
asyncSequences: Add Timer AsyncSequence
1 parent c09db2c commit ebb50a0

4 files changed

Lines changed: 151 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
**v0.3.0 - Beryllium:**
22

33
- new Share operator
4+
- new Timer AsyncSequence
45

56
**v0.2.1 - Lithium:**
67

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ AsyncSequences
2929
* [Zip2](#Zip2)
3030
* [Zip3](#Zip3)
3131
* [Zip](#Zip)
32+
* [Timer](#Timer)
3233

3334
### Async Streams
3435
* [Passthrough](#Passthrough)
@@ -202,6 +203,28 @@ for try await element in zippedAsyncSequence {
202203
}
203204
```
204205

206+
### Timer
207+
208+
`Timer` is an async sequence that repeatedly emits the current date on the given interval, with the given priority.
209+
210+
```swift
211+
let timer = AsyncSequences.Timer(priority: .high, every: .seconds(1))
212+
213+
Task {
214+
for try await element in timer {
215+
print(element)
216+
}
217+
}
218+
219+
// will print:
220+
// 2022-03-06 19:31:22 +0000
221+
// 2022-03-06 19:31:23 +0000
222+
// 2022-03-06 19:31:24 +0000
223+
// 2022-03-06 19:31:25 +0000
224+
// 2022-03-06 19:31:26 +0000
225+
// and will stop once timer.cancel() is called or the parent task is cancelled.
226+
```
227+
205228
## Async Streams
206229

207230
### Passthrough
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// AsyncSequences+Timer.swift
3+
//
4+
//
5+
// Created by Thibault Wittemberg on 04/03/2022.
6+
//
7+
8+
import Foundation
9+
10+
public extension AsyncSequences {
11+
typealias Timer = AsyncTimerSequence
12+
}
13+
14+
/// `AsyncTimerSequence`is an async sequence that repeatedly emits the current date on the given interval, with the given priority.
15+
public class AsyncTimerSequence: AsyncSequence {
16+
public typealias Element = Date
17+
public typealias AsyncIterator = AsyncThrowingStream<Date, Error>.AsyncIterator
18+
19+
let priority: TaskPriority?
20+
let interval: AsyncSequences.Interval
21+
var task: Task<Void, Error>?
22+
23+
/// Created an async sequence that repeatedly emits the current date on the given interval, with the given priority.
24+
///
25+
/// ```
26+
/// let timer = AsyncSequences.Timer(priority: .high, every: .seconds(1))
27+
///
28+
/// Task {
29+
/// for try await element in timer {
30+
/// print(element)
31+
/// }
32+
/// }
33+
///
34+
/// // will print:
35+
/// // 2022-03-06 19:31:22 +0000
36+
/// // 2022-03-06 19:31:23 +0000
37+
/// // 2022-03-06 19:31:24 +0000
38+
/// // 2022-03-06 19:31:25 +0000
39+
/// // 2022-03-06 19:31:26 +0000
40+
/// // and will stop once timer.cancel() is called or the parent task is cancelled.
41+
/// ```
42+
///
43+
/// - Parameters:
44+
/// - priority: The priority of the inderlying Task. Nil by default.
45+
/// - interval: The time interval on which to publish events. For example, a value of `0.5` publishes an event approximately every half-second.
46+
/// - Returns: An async sequence that repeatedly emits the current date on the given interval, with the given priority.
47+
public init(priority: TaskPriority? = nil, every interval: AsyncSequences.Interval) {
48+
self.priority = priority
49+
self.interval = interval
50+
}
51+
52+
func makeStream() -> AsyncThrowingStream<Date, Error> {
53+
AsyncThrowingStream<Date, Error>(Date.self, bufferingPolicy: .unbounded) { [weak self] continuation in
54+
let interval = self?.interval ?? .immediate
55+
self?.task = Task(priority: self?.priority) {
56+
do {
57+
while !Task.isCancelled {
58+
try await Task.sleep(nanoseconds: interval.value)
59+
continuation.yield(Date())
60+
}
61+
} catch is CancellationError {
62+
continuation.finish()
63+
} catch {
64+
throw error
65+
}
66+
}
67+
}
68+
}
69+
70+
/// Cancels the timer.
71+
public func cancel() {
72+
self.task?.cancel()
73+
}
74+
75+
public func makeAsyncIterator() -> AsyncIterator {
76+
self.makeStream().makeAsyncIterator()
77+
}
78+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// AsyncSequences+TimerTests.swift
3+
//
4+
//
5+
// Created by Thibault Wittemberg on 06/03/2022.
6+
//
7+
8+
import AsyncExtensions
9+
import XCTest
10+
11+
final class AsyncSequences_TimerTests: XCTestCase {
12+
func testTimer_finishes_when_task_is_cancelled() {
13+
let canCancelExpectation = expectation(description: "the timer can be cancelled")
14+
let asyncSequenceHasFinishedExpectation = expectation(description: "The async sequence has finished")
15+
16+
let sut = AsyncSequences.Timer(priority: .userInitiated, every: .milliSeconds(100))
17+
18+
let task = Task {
19+
var index = 1
20+
for try await _ in sut {
21+
if index == 10 {
22+
canCancelExpectation.fulfill()
23+
}
24+
index += 1
25+
}
26+
asyncSequenceHasFinishedExpectation.fulfill()
27+
}
28+
29+
wait(for: [canCancelExpectation], timeout: 5)
30+
31+
task.cancel()
32+
33+
wait(for: [asyncSequenceHasFinishedExpectation], timeout: 5)
34+
}
35+
36+
func testTimer_finishes_when_cancel_is_called() async throws {
37+
let sut = AsyncSequences.Timer(priority: .userInitiated, every: .milliSeconds(100))
38+
39+
var index = 1
40+
for try await _ in sut {
41+
if index == 10 {
42+
sut.cancel()
43+
}
44+
index += 1
45+
}
46+
47+
XCTAssertEqual(index, 11)
48+
}
49+
}

0 commit comments

Comments
 (0)