+5
.changeset/yellow-hounds-heal.md
+5
.changeset/yellow-hounds-heal.md
···
1
+
---
2
+
'wonka': patch
3
+
---
4
+
5
+
Improve compatibility of `fromAsyncIterable` and `toAsyncIterable`. The `toAsyncIterable` will now output an object that's both an `AsyncIterator` and an `AsyncIterable`. Both helpers will now use a polyfill for `Symbol.asyncIterator` to improve compatibility with the Hermes engine and Babel transpilation.
+10
-7
src/__tests__/sinks.test.ts
+10
-7
src/__tests__/sinks.test.ts
···
244
244
};
245
245
246
246
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
247
+
const next$ = asyncIterator.next();
247
248
249
+
sink!(push(0));
250
+
expect(await next$).toEqual({ value: 0, done: false });
248
251
expect(pulls).toBe(1);
249
-
sink!(push(0));
250
-
expect(await asyncIterator.next()).toEqual({ value: 0, done: false });
251
-
expect(pulls).toBe(2);
252
252
253
253
sink!(push(1));
254
254
expect(await asyncIterator.next()).toEqual({ value: 1, done: false });
255
-
expect(pulls).toBe(3);
255
+
expect(pulls).toBe(2);
256
256
257
257
sink!(SignalKind.End);
258
258
expect(await asyncIterator.next()).toEqual({ done: true });
259
-
expect(pulls).toBe(3);
259
+
expect(pulls).toBe(2);
260
260
});
261
261
262
262
it('buffers actively pushed values', async () => {
···
273
273
};
274
274
275
275
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
276
+
const next$ = asyncIterator.next();
276
277
277
278
sink!(push(0));
278
279
sink!(push(1));
279
280
sink!(SignalKind.End);
280
281
281
282
expect(pulls).toBe(1);
282
-
expect(await asyncIterator.next()).toEqual({ value: 0, done: false });
283
+
expect(await next$).toEqual({ value: 0, done: false });
283
284
expect(await asyncIterator.next()).toEqual({ value: 1, done: false });
284
285
expect(await asyncIterator.next()).toEqual({ done: true });
285
286
});
···
298
299
};
299
300
300
301
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
302
+
asyncIterator.next();
301
303
expect(pulls).toBe(1);
302
304
303
305
let resolved = false;
···
330
332
};
331
333
332
334
const asyncIterator = sinks.toAsyncIterable(source)[Symbol.asyncIterator]();
335
+
const next$ = asyncIterator.next();
333
336
334
337
sink!(push(0));
335
-
expect(await asyncIterator.next()).toEqual({ value: 0, done: false });
338
+
expect(await next$).toEqual({ value: 0, done: false });
336
339
expect(await asyncIterator.return!()).toEqual({ done: true });
337
340
338
341
sink!(push(1));
+27
src/helpers.ts
+27
src/helpers.ts
···
1
1
import { TalkbackFn, TeardownFn, Start, Push, SignalKind } from './types';
2
2
3
+
declare global {
4
+
interface SymbolConstructor {
5
+
readonly observable: symbol;
6
+
}
7
+
}
8
+
3
9
/** Placeholder {@link TeardownFn | teardown functions} that's a no-op.
4
10
* @see {@link TeardownFn} for the definition and usage of teardowns.
5
11
* @internal
···
39
45
0: value,
40
46
} as Push<T>;
41
47
}
48
+
49
+
/** Returns the well-known symbol specifying the default AsyncIterator.
50
+
* @internal
51
+
*/
52
+
export const asyncIteratorSymbol = (): typeof Symbol.asyncIterator =>
53
+
(typeof Symbol === 'function' && Symbol.asyncIterator) || ('@@asyncIterator' as any);
54
+
55
+
/** Returns the well-known symbol specifying the default ES Observable.
56
+
* @privateRemarks
57
+
* This symbol is used to mark an object as a default ES Observable. By the specification, an object
58
+
* that abides by the default Observable implementation must carry a method set to this well-known
59
+
* symbol that returns the Observable implementation. It's common for this object to be an
60
+
* Observable itself and return itself on this method.
61
+
*
62
+
* @see {@link https://github.com/0no-co/wonka/issues/122} for notes on the intercompatibility
63
+
* between Observable implementations.
64
+
*
65
+
* @internal
66
+
*/
67
+
export const observableSymbol = (): typeof Symbol.observable =>
68
+
(typeof Symbol === 'function' && Symbol.observable) || ('@@observable' as any);
+1
-22
src/observable.ts
+1
-22
src/observable.ts
···
1
1
import { Source, SignalKind, TalkbackKind } from './types';
2
-
import { push, start, talkbackPlaceholder } from './helpers';
3
-
4
-
declare global {
5
-
interface SymbolConstructor {
6
-
readonly observable: symbol;
7
-
}
8
-
}
2
+
import { push, start, talkbackPlaceholder, observableSymbol } from './helpers';
9
3
10
4
/** A definition of the ES Observable Subscription type that is returned by
11
5
* {@link Observable.subscribe}
···
117
111
/** The well-known symbol specifying the default ES Observable for an object. */
118
112
[Symbol.observable](): Observable<T>;
119
113
}
120
-
121
-
/** Returns the well-known symbol specifying the default ES Observable.
122
-
* @privateRemarks
123
-
* This symbol is used to mark an object as a default ES Observable. By the specification, an object
124
-
* that abides by the default Observable implementation must carry a method set to this well-known
125
-
* symbol that returns the Observable implementation. It's common for this object to be an
126
-
* Observable itself and return itself on this method.
127
-
*
128
-
* @see {@link https://github.com/0no-co/wonka/issues/122} for notes on the intercompatibility
129
-
* between Observable implementations.
130
-
*
131
-
* @internal
132
-
*/
133
-
const observableSymbol = (): typeof Symbol.observable =>
134
-
Symbol.observable || ('@@observable' as any);
135
114
136
115
/** Converts an ES Observable to a {@link Source}.
137
116
* @param input - The {@link ObservableLike} object that will be converted.
+51
-40
src/sinks.ts
+51
-40
src/sinks.ts
···
1
-
import { Source, Subscription, TalkbackKind, SignalKind } from './types';
2
-
import { talkbackPlaceholder } from './helpers';
1
+
import { Source, Subscription, TalkbackKind, SignalKind, SourceIterable } from './types';
2
+
import { talkbackPlaceholder, asyncIteratorSymbol } from './helpers';
3
3
4
4
/** Creates a subscription to a given source and invokes a `subscriber` callback for each value.
5
5
* @param subscriber - A callback function called for each issued value.
···
124
124
* }
125
125
* ```
126
126
*/
127
-
export const toAsyncIterable = <T>(source: Source<T>): AsyncIterable<T> => ({
128
-
[Symbol.asyncIterator](): AsyncIterator<T> {
129
-
const buffer: T[] = [];
127
+
export const toAsyncIterable = <T>(source: Source<T>): SourceIterable<T> => {
128
+
const buffer: T[] = [];
130
129
131
-
let ended = false;
132
-
let talkback = talkbackPlaceholder;
133
-
let next: ((value: IteratorResult<T>) => void) | void;
130
+
let ended = false;
131
+
let started = false;
132
+
let pulled = false;
133
+
let talkback = talkbackPlaceholder;
134
+
let next: ((value: IteratorResult<T>) => void) | void;
134
135
135
-
source(signal => {
136
-
if (ended) {
137
-
/*noop*/
138
-
} else if (signal === SignalKind.End) {
139
-
if (next) next = next(doneResult);
140
-
ended = true;
141
-
} else if (signal.tag === SignalKind.Start) {
142
-
(talkback = signal[0])(TalkbackKind.Pull);
143
-
} else if (next) {
144
-
next = next({ value: signal[0], done: false });
145
-
} else {
146
-
buffer.push(signal[0]);
136
+
return {
137
+
async next(): Promise<IteratorResult<T>> {
138
+
if (!started) {
139
+
started = true;
140
+
source(signal => {
141
+
if (ended) {
142
+
/*noop*/
143
+
} else if (signal === SignalKind.End) {
144
+
if (next) next = next(doneResult);
145
+
ended = true;
146
+
} else if (signal.tag === SignalKind.Start) {
147
+
pulled = true;
148
+
(talkback = signal[0])(TalkbackKind.Pull);
149
+
} else {
150
+
pulled = false;
151
+
if (next) {
152
+
next = next({ value: signal[0], done: false });
153
+
} else {
154
+
buffer.push(signal[0]);
155
+
}
156
+
}
157
+
});
147
158
}
148
-
});
149
159
150
-
return {
151
-
async next(): Promise<IteratorResult<T>> {
152
-
if (ended && !buffer.length) {
153
-
return doneResult;
154
-
} else if (!ended && buffer.length <= 1) {
155
-
talkback(TalkbackKind.Pull);
156
-
}
160
+
if (ended && !buffer.length) {
161
+
return doneResult;
162
+
} else if (!ended && !pulled && buffer.length <= 1) {
163
+
pulled = true;
164
+
talkback(TalkbackKind.Pull);
165
+
}
157
166
158
-
return buffer.length
159
-
? { value: buffer.shift()!, done: false }
160
-
: new Promise(resolve => (next = resolve));
161
-
},
162
-
async return(): Promise<IteratorReturnResult<void>> {
163
-
if (!ended) next = talkback(TalkbackKind.Close);
164
-
ended = true;
165
-
return doneResult;
166
-
},
167
-
};
168
-
},
169
-
});
167
+
return buffer.length
168
+
? { value: buffer.shift()!, done: false }
169
+
: new Promise(resolve => (next = resolve));
170
+
},
171
+
async return(): Promise<IteratorReturnResult<void>> {
172
+
if (!ended) next = talkback(TalkbackKind.Close);
173
+
ended = true;
174
+
return doneResult;
175
+
},
176
+
[asyncIteratorSymbol()](): SourceIterable<T> {
177
+
return this;
178
+
},
179
+
};
180
+
};
170
181
171
182
/** Subscribes to a given source and collects all synchronous values into an array.
172
183
* @param source - A {@link Source}.
+11
-3
src/sources.ts
+11
-3
src/sources.ts
···
1
1
import { Source, Sink, SignalKind, TalkbackKind, Observer, Subject, TeardownFn } from './types';
2
-
import { push, start, talkbackPlaceholder, teardownPlaceholder } from './helpers';
2
+
import {
3
+
push,
4
+
start,
5
+
talkbackPlaceholder,
6
+
teardownPlaceholder,
7
+
asyncIteratorSymbol,
8
+
} from './helpers';
3
9
import { share } from './operators';
4
10
5
11
/** Helper creating a Source from a factory function when it's subscribed to.
···
45
51
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols}
46
52
* for the JS Iterable protocol.
47
53
*/
48
-
export function fromAsyncIterable<T>(iterable: AsyncIterable<T>): Source<T> {
54
+
export function fromAsyncIterable<T>(iterable: AsyncIterable<T> | AsyncIterator<T>): Source<T> {
49
55
return sink => {
50
-
const iterator = iterable[Symbol.asyncIterator]();
56
+
const iterator: AsyncIterator<T> =
57
+
(iterable[asyncIteratorSymbol()] && iterable[asyncIteratorSymbol()]()) || iterable;
58
+
51
59
let ended = false;
52
60
let looping = false;
53
61
let pulled = false;
+5
src/types.d.ts
+5
src/types.d.ts
···
200
200
/** The {@link Source} that issues the signals as the {@link Observer} methods are called. */
201
201
source: Source<T>;
202
202
}
203
+
204
+
/** Async Iterable/Iterator after having converted a {@link Source}.
205
+
* @see {@link toAsyncIterable} for a helper that creates this structure.
206
+
*/
207
+
export interface SourceIterable<T> extends AsyncIterator<T>, AsyncIterable<T> {}