-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy patheach.ts
More file actions
127 lines (113 loc) · 3.48 KB
/
Copy patheach.ts
File metadata and controls
127 lines (113 loc) · 3.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { spawn } from "./spawn.ts";
import { constant } from "./constant.ts";
import { createContext } from "./context.ts";
import { useScope } from "./scope.ts";
import type { Operation, Stream, Subscription } from "./types.ts";
import { withResolvers } from "./with-resolvers.ts";
/**
* Consume an effection stream using a simple for-of loop.
*
* Given any stream, you can access its values sequentially using the `each()`
* operation just as you would use `for await of` loop with an async iterable:
*
* ```javascript
* function* logvalues(stream) {
* for (let value of yield* each(stream)) {
* console.log(value);
* yield* each.next()
* }
* }
* ```
* You must always invoke `each.next` at the end of each iteration of the loop,
* including if the interation ends with a `continue` statement.
*
* Note that just as with async iterators, there is no way to consume the
* `TClose` value of a stream using the `for-each` loop.
*
* @typeParam T - the type of each value in the stream.
* @param stream - the stream to iterate
* @returns an operation to iterate `stream`
*/
export function each<T>(
enumerable: Stream<T, unknown> | Subscription<T, unknown>,
): Operation<Iterable<T>> {
return {
*[Symbol.iterator]() {
let stream = typeof (enumerable as Subscription<T, unknown>).next ===
"function"
? constant(enumerable as Subscription<T, unknown>)
: enumerable as Stream<T, unknown>;
let scope = yield* useScope();
if (!scope.hasOwn(EachStack)) {
scope.set(EachStack, []);
}
let done = withResolvers<void>();
let cxt = withResolvers<EachLoop<T>>();
yield* spawn(function* () {
let subscription = yield* stream;
let current = yield* subscription.next();
let stack = scope.expect(EachStack);
let context: EachLoop<T> = {
subscription,
current,
finish() {
context.finish = () => {};
stack.pop();
done.resolve();
},
};
stack.push(context);
cxt.resolve(context);
yield* done.operation;
});
let context = yield* cxt.operation;
return {
[Symbol.iterator]: () => ({
next() {
if (context.stale) {
let error = new Error(
`for each loop did not use each.next() operation before continuing`,
);
error.name = "IterationError";
throw error;
} else {
context.stale = true;
return context.current;
}
},
return() {
context.finish();
return { done: true, value: void 0 };
},
}),
};
},
};
}
each.next = function next(): Operation<void> {
return {
name: "each.next()",
*[Symbol.iterator]() {
let stack = yield* EachStack.expect();
let context = stack[stack.length - 1];
if (!context) {
let error = new Error(`cannot call next() outside of an iteration`);
error.name = "IterationError";
throw error;
}
let current = yield* context.subscription.next();
delete context.stale;
context.current = current;
if (current.done) {
context.finish();
}
},
} as Operation<void>;
};
interface EachLoop<T> {
subscription: Subscription<T, unknown>;
current: IteratorResult<T>;
finish: () => void;
stale?: true;
}
const EachStack = createContext<EachLoop<unknown>[]>("each");