Skip to content

Commit 696e8cc

Browse files
committed
🦄 refactor: Remove reliance on generators in Effected implementation
1 parent b08bd44 commit 696e8cc

File tree

1 file changed

+61
-18
lines changed

1 file changed

+61
-18
lines changed

src/effected.ts

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -430,13 +430,18 @@ export class Effected<out E extends Effect, out R> implements Iterable<E, R, unk
430430
...effect.payloads,
431431
);
432432

433-
if (!(handlerResult instanceof Effected) && !isGenerator(handlerResult))
433+
if (
434+
!(handlerResult instanceof Effected) &&
435+
!isGenerator(handlerResult) &&
436+
!isEffectedIterator(handlerResult)
437+
)
434438
return { done: false, value: constructHandledEffect() } as never;
435439

436-
const it = handlerResult[Symbol.iterator]();
440+
const iter =
441+
Symbol.iterator in handlerResult ? handlerResult[Symbol.iterator]() : handlerResult;
437442
interceptIterator = {
438443
next: (...args: [] | [unknown]) => {
439-
const result = it.next(...args);
444+
const result = iter.next(...args);
440445
if (result.done) {
441446
interceptIterator = null;
442447
return { done: false, value: constructHandledEffect() } as never;
@@ -480,9 +485,15 @@ export class Effected<out E extends Effect, out R> implements Iterable<E, R, unk
480485
return this.handle(effect, (({ resume }: any, ...payloads: unknown[]) => {
481486
const it = handler(...payloads);
482487
if (!(it instanceof Effected) && !isGenerator(it)) return resume(it);
483-
return (function* () {
484-
resume(yield* it);
485-
})();
488+
const iterator = it[Symbol.iterator]();
489+
return {
490+
_effectedIterator: true,
491+
next: (...args: [] | [unknown]) => {
492+
const result = iterator.next(...args);
493+
if (result.done) return { done: true, value: resume(result.value) };
494+
return result;
495+
},
496+
};
486497
}) as never);
487498
}
488499

@@ -521,9 +532,15 @@ export class Effected<out E extends Effect, out R> implements Iterable<E, R, unk
521532
return this.handle(effect, (({ terminate }: any, ...payloads: unknown[]) => {
522533
const it = handler(...payloads);
523534
if (!(it instanceof Effected) && !isGenerator(it)) return terminate(it);
524-
return (function* () {
525-
terminate(yield* it);
526-
})();
535+
const iterator = it[Symbol.iterator]();
536+
return {
537+
_effectedIterator: true,
538+
next: (...args: [] | [unknown]) => {
539+
const result = iterator.next(...args);
540+
if (result.done) return { done: true, value: terminate(result.value) };
541+
return result;
542+
},
543+
};
527544
}) as never);
528545
}
529546

@@ -549,8 +566,9 @@ export class Effected<out E extends Effect, out R> implements Iterable<E, R, unk
549566
if (!result.done) return result;
550567
originalIteratorDone = true;
551568
const it = handler(result.value);
552-
if (!(it instanceof Effected) && !isGenerator(it)) return { done: true, value: it };
553-
appendedIterator = it[Symbol.iterator]();
569+
if (!(it instanceof Effected) && !isGenerator(it) && !isEffectedIterator(it))
570+
return { done: true, value: it };
571+
appendedIterator = Symbol.iterator in it ? it[Symbol.iterator]() : it;
554572
return appendedIterator.next();
555573
},
556574
};
@@ -570,10 +588,15 @@ export class Effected<out E extends Effect, out R> implements Iterable<E, R, unk
570588
return this.map((value) => {
571589
const it = handler(value);
572590
if (!(it instanceof Effected) && !isGenerator(it)) return value;
573-
return (function* () {
574-
yield* it;
575-
return value;
576-
})();
591+
const iterator = it[Symbol.iterator]();
592+
return {
593+
_effectedIterator: true,
594+
next: (...args: [] | [unknown]) => {
595+
const result = iterator.next(...args);
596+
if (result.done) return { done: true, value };
597+
return result;
598+
},
599+
};
577600
}) as never;
578601
}
579602

@@ -617,9 +640,15 @@ export class Effected<out E extends Effect, out R> implements Iterable<E, R, unk
617640
const error = effect.name.slice(6) as ErrorName<E>;
618641
const it = handler(error, ...payloads);
619642
if (!(it instanceof Effected) && !isGenerator(it)) return terminate(it);
620-
return (function* () {
621-
terminate(yield* it);
622-
})();
643+
const iterator = it[Symbol.iterator]();
644+
return {
645+
_effectedIterator: true,
646+
next: (...args: [] | [unknown]) => {
647+
const result = iterator.next(...args);
648+
if (result.done) return { done: true, value: terminate(result.value) };
649+
return result;
650+
},
651+
};
623652
}) as never,
624653
);
625654
}
@@ -1083,6 +1112,20 @@ export function runAsync<E extends Effected<Effect, unknown>>(
10831112
const isGenerator = (value: unknown): value is Generator =>
10841113
Object.prototype.toString.call(value) === "[object Generator]";
10851114

1115+
/**
1116+
* Check if a value is an `EffectedIterator` (i.e., an {@link Iterator} with an `_effectedIterator`
1117+
* property set to `true`).
1118+
*
1119+
* This is only used internally as an alternative to generators to reduce the overhead of creating
1120+
* generator functions.
1121+
* @param value
1122+
* @returns
1123+
*/
1124+
const isEffectedIterator = (
1125+
value: unknown,
1126+
): value is Iterator<Effect, unknown, unknown> & { _effectedIterator: true } =>
1127+
typeof value === "object" && value !== null && (value as any)._effectedIterator === true;
1128+
10861129
/**
10871130
* Capitalize the first letter of a string.
10881131
* @param str The string to capitalize.

0 commit comments

Comments
 (0)