MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1import { test, testDeep, summary } from './helpers.js';
2import { inspect } from 'node:util';
3
4console.log('Async Iterator Tests\n');
5
6test('AsyncIterator global exists', typeof AsyncIterator, 'function');
7test('AsyncIterator prototype async iterator tag', AsyncIterator.prototype[Symbol.toStringTag], 'AsyncIterator');
8
9async function* protoShapeAsyncGen() {
10 yield 1;
11}
12const AsyncGeneratorFunction = protoShapeAsyncGen.constructor;
13const protoShapeAsyncIter = protoShapeAsyncGen();
14const ownAsyncGeneratorProto = Object.getPrototypeOf(protoShapeAsyncIter);
15const sharedAsyncGeneratorProto = Object.getPrototypeOf(ownAsyncGeneratorProto);
16const inheritedAsyncIteratorProto = Object.getPrototypeOf(sharedAsyncGeneratorProto);
17test('async generator function has prototype property', protoShapeAsyncGen.hasOwnProperty('prototype'), true);
18test('async generator function constructor name', AsyncGeneratorFunction.name, 'AsyncGeneratorFunction');
19test('async generator function prototype chain', Object.getPrototypeOf(protoShapeAsyncGen), AsyncGeneratorFunction.prototype);
20test('async generator function prototype tag', AsyncGeneratorFunction.prototype[Symbol.toStringTag], 'AsyncGeneratorFunction');
21test('async generator function inspect tag', inspect(protoShapeAsyncGen), '[AsyncGeneratorFunction: protoShapeAsyncGen]');
22test('async generator function prototype prototype', AsyncGeneratorFunction.prototype.prototype, sharedAsyncGeneratorProto);
23test(
24 'new async generator function throws',
25 (() => {
26 try {
27 new protoShapeAsyncGen();
28 return false;
29 } catch (err) {
30 return true;
31 }
32 })(),
33 true
34);
35test('async generator instance uses function prototype', ownAsyncGeneratorProto, protoShapeAsyncGen.prototype);
36test('async generator shared prototype tag', sharedAsyncGeneratorProto[Symbol.toStringTag], 'AsyncGenerator');
37test('async generator shared prototype has next', sharedAsyncGeneratorProto.hasOwnProperty('next'), true);
38test('async generator shared prototype inherits AsyncIterator', inheritedAsyncIteratorProto, AsyncIterator.prototype);
39test('async generator Symbol.asyncIterator inherited from AsyncIterator', protoShapeAsyncIter[Symbol.asyncIterator](), protoShapeAsyncIter);
40test('async generator inherits AsyncIterator helpers', typeof protoShapeAsyncIter.map, 'function');
41
42const dynamicAsyncGen = AsyncGeneratorFunction('yield 4;');
43const dynamicAsyncIter = dynamicAsyncGen();
44test('AsyncGeneratorFunction constructs async generator function', Object.getPrototypeOf(dynamicAsyncGen), AsyncGeneratorFunction.prototype);
45test('AsyncGeneratorFunction instance uses function prototype', Object.getPrototypeOf(dynamicAsyncIter), dynamicAsyncGen.prototype);
46test('AsyncGeneratorFunction yielded value', (await dynamicAsyncIter.next()).value, 4);
47test(
48 'strict async generator function expression has own prototype',
49 Function('"use strict"; return (async function* () {}).hasOwnProperty("prototype");')(),
50 true
51);
52
53class CustomAsyncIterator extends AsyncIterator {
54 constructor(values) {
55 super();
56 this.values = values;
57 this.index = 0;
58 }
59
60 next() {
61 if (this.index >= this.values.length) return Promise.resolve({ done: true });
62 return Promise.resolve({ done: false, value: this.values[this.index++] });
63 }
64}
65
66const custom = new CustomAsyncIterator([4, 5]);
67test('class extends AsyncIterator', custom[Symbol.asyncIterator](), custom);
68test('class instance instanceof AsyncIterator', custom instanceof AsyncIterator, true);
69testDeep('custom async iterator toArray', await custom.toArray(), [4, 5]);
70
71const asyncSource = AsyncIterator.from(
72 (async function* () {
73 yield 1;
74 yield 2;
75 yield 3;
76 })()
77);
78test('AsyncIterator.from async iterable instanceof', asyncSource instanceof AsyncIterator, true);
79testDeep('AsyncIterator.from async iterable values', await asyncSource.toArray(), [1, 2, 3]);
80
81testDeep('AsyncIterator.from iterable values', await AsyncIterator.from([1, 2, 3]).toArray(), [1, 2, 3]);
82testDeep('AsyncIterator.from iterator values', await AsyncIterator.from([1, 2, 3].values()).toArray(), [1, 2, 3]);
83
84const promisedStep = await AsyncIterator.from([Promise.resolve(7)]).next();
85test('AsyncIterator.from sync iterable awaits promised value', promisedStep.value, 7);
86test('AsyncIterator.from sync iterable promised value done', promisedStep.done, false);
87
88let rejectedValueCaught;
89try {
90 await AsyncIterator.from([Promise.reject(new Error('sync value rejection'))]).next();
91} catch (e) {
92 rejectedValueCaught = e;
93}
94test('AsyncIterator.from sync iterable rejects promised value', rejectedValueCaught.message, 'sync value rejection');
95
96const syncReturnSource = {
97 next() {
98 return { done: false, value: 0 };
99 },
100 return() {
101 return { done: true, value: Promise.resolve(7) };
102 },
103 [Symbol.iterator]() {
104 return this;
105 }
106};
107test('AsyncIterator.from sync return awaits value', (await AsyncIterator.from(syncReturnSource).return()).value, 7);
108
109const syncThrowSource = {
110 next() {
111 return { done: false, value: 0 };
112 },
113 throw() {
114 return { done: true, value: Promise.resolve(8) };
115 },
116 [Symbol.iterator]() {
117 return this;
118 }
119};
120test('AsyncIterator.from sync throw awaits value', (await AsyncIterator.from(syncThrowSource).throw(new Error('x'))).value, 8);
121
122testDeep(
123 'async iterator map',
124 await AsyncIterator.from([1, 2, 3])
125 .map(x => x * x)
126 .toArray(),
127 [1, 4, 9]
128);
129testDeep(
130 'async iterator map awaits callback',
131 await AsyncIterator.from([1, 2])
132 .map(async x => x + 1)
133 .toArray(),
134 [2, 3]
135);
136testDeep(
137 'async iterator filter',
138 await AsyncIterator.from([1, 2, 3, 4])
139 .filter(x => x % 2)
140 .toArray(),
141 [1, 3]
142);
143testDeep(
144 'async iterator filter awaits callback',
145 await AsyncIterator.from([1, 2, 3, 4])
146 .filter(async x => x > 2)
147 .toArray(),
148 [3, 4]
149);
150testDeep('async iterator take', await AsyncIterator.from([1, 2, 3]).take(2).toArray(), [1, 2]);
151testDeep('async iterator drop', await AsyncIterator.from([1, 2, 3]).drop(1).toArray(), [2, 3]);
152testDeep(
153 'async iterator flatMap sync inner',
154 await AsyncIterator.from([1, 2, 3])
155 .flatMap(x => [x, 0])
156 .toArray(),
157 [1, 0, 2, 0, 3, 0]
158);
159testDeep(
160 'async iterator flatMap awaits callback',
161 await AsyncIterator.from([1, 2])
162 .flatMap(async x => [x, x + 10])
163 .toArray(),
164 [1, 11, 2, 12]
165);
166
167async function* doubleAsync(x) {
168 yield x;
169 yield x * 2;
170}
171
172testDeep('async iterator flatMap async inner', await AsyncIterator.from([2, 3]).flatMap(doubleAsync).toArray(), [2, 4, 3, 6]);
173
174test('async iterator every true', await AsyncIterator.from([1, 2, 3]).every(x => typeof x === 'number'), true);
175test('async iterator every awaits callback', await AsyncIterator.from([1, 2, 3]).every(async x => x < 4), true);
176test('async iterator some true', await AsyncIterator.from([1, 2, 3]).some(x => x === 2), true);
177test('async iterator some awaits callback', await AsyncIterator.from([1, 2, 3]).some(async x => x === 3), true);
178test('async iterator find', await AsyncIterator.from([1, 2, 3]).find(x => x > 1), 2);
179test('async iterator find awaits callback', await AsyncIterator.from([1, 2, 3]).find(async x => x > 2), 3);
180
181function closableAsyncIterator(log) {
182 return {
183 index: 0,
184 next() {
185 return Promise.resolve({ done: false, value: ++this.index });
186 },
187 return() {
188 log.push('closed');
189 return Promise.resolve({ done: true });
190 },
191 [Symbol.asyncIterator]() {
192 return this;
193 }
194 };
195}
196
197let everyCloseLog = [];
198test('async iterator every closes on false', await AsyncIterator.from(closableAsyncIterator(everyCloseLog)).every(x => x < 1), false);
199testDeep('async iterator every close log', everyCloseLog, ['closed']);
200
201let someCloseLog = [];
202test('async iterator some closes on match', await AsyncIterator.from(closableAsyncIterator(someCloseLog)).some(x => x === 1), true);
203testDeep('async iterator some close log', someCloseLog, ['closed']);
204
205let findCloseLog = [];
206test('async iterator find closes on match', await AsyncIterator.from(closableAsyncIterator(findCloseLog)).find(x => x === 1), 1);
207testDeep('async iterator find close log', findCloseLog, ['closed']);
208
209let throwCloseLog = [];
210let throwCaught = false;
211try {
212 await AsyncIterator.from(closableAsyncIterator(throwCloseLog)).some(() => {
213 throw new Error('sync callback failure');
214 });
215} catch (err) {
216 throwCaught = err.message === 'sync callback failure';
217}
218test('async iterator closes on sync callback throw', throwCaught, true);
219testDeep('async iterator sync callback throw close log', throwCloseLog, ['closed']);
220
221let rejectCloseLog = [];
222let rejectCaught = false;
223try {
224 await AsyncIterator.from(closableAsyncIterator(rejectCloseLog)).find(async () => {
225 throw new Error('async callback failure');
226 });
227} catch (err) {
228 rejectCaught = err.message === 'async callback failure';
229}
230test('async iterator closes on async callback rejection', rejectCaught, true);
231testDeep('async iterator async callback rejection close log', rejectCloseLog, ['closed']);
232
233const syncAsyncLike = {
234 index: 0,
235 next() {
236 if (this.index >= 5000) return { done: true };
237 return { done: false, value: this.index++ };
238 },
239 [Symbol.asyncIterator]() {
240 return this;
241 }
242};
243test('async iterator handles sync next results without recursive stack growth', (await AsyncIterator.from(syncAsyncLike).toArray()).length, 5000);
244
245let forEachSum = 0;
246await AsyncIterator.from([1, 2, 3]).forEach(x => {
247 forEachSum += x;
248});
249test('async iterator forEach', forEachSum, 6);
250
251let asyncForEachSum = 0;
252await AsyncIterator.from([1, 2, 3]).forEach(async x => {
253 asyncForEachSum += x;
254});
255test('async iterator forEach awaits callback', asyncForEachSum, 6);
256
257test('async iterator reduce', await AsyncIterator.from([1, 2, 3]).reduce((a, b) => a + b), 6);
258test('async iterator reduce awaits callback', await AsyncIterator.from([1, 2, 3]).reduce(async (a, b) => a + b), 6);
259testDeep(
260 'async generator helpers inherited',
261 await (async function* () {
262 yield 2;
263 yield 4;
264 })()
265 .map(x => x + 1)
266 .toArray(),
267 [3, 5]
268);
269
270summary();