Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-fn-iterator-result.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"effect": patch
---

fix(Effect): handle transpiled generator bodies in `Effect.fn`

`Effect.fn(name)(body)` crashed at runtime with `RuntimeException: Not a valid effect: {}` when `body` was a generator function lowered by a compiler (e.g. `babel-preset-expo` on React Native / Hermes) into a plain function returning an iterator IIFE. Such a body fails the `isGeneratorFunction` check, so its return value was passed through as if it were an `Effect`. We now duck-type the result and re-wrap it with `fromIterator` when it is an iterator.
25 changes: 24 additions & 1 deletion packages/effect/src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14707,7 +14707,30 @@ function fnApply(options: {
effect = core.fromIterator(() => options.body.apply(options.self, options.args))
} else {
try {
effect = options.body.apply(options.self, options.args)
const result = options.body.apply(options.self, options.args)
// Some compilers (e.g. `babel-preset-expo` on React Native / Hermes)
// lower destructured-param generators into a plain function that returns
// an iterator IIFE. Such a body fails `isGeneratorFunction` but its
// return value is an iterator, not an Effect. Detect and re-wrap.
if (
result !== null &&
typeof result === "object" &&
(result as any)[EffectTypeId] === undefined &&
typeof (result as any).next === "function" &&
typeof (result as any)[Symbol.iterator] === "function"
) {
let firstIterator: any = result
effect = core.fromIterator(() => {
if (firstIterator !== null) {
const it = firstIterator
firstIterator = null
return it
}
return options.body.apply(options.self, options.args)
})
} else {
effect = result
}
} catch (error) {
fnError = error
effect = die(error)
Expand Down
17 changes: 17 additions & 0 deletions packages/effect/test/Effect/fn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ describe("Effect.fn", () => {
strictEqual(fn2.length, 1)
strictEqual(Effect.runSync(fn2(2)), 2)
})

it.effect("handles a non-generator body that returns an iterator (transpiled generator)", () =>
Effect.gen(function*() {
// Mimics `babel-preset-expo` lowering `function*({a}) { return a }` into
// a plain function that returns a generator IIFE iterator.
const body = function(arg: { a: number }) {
const a = arg.a
return (function*() {
return a
})()
}
const fn = Effect.fn("test")(body as any)
const v = yield* fn({ a: 42 }) as Effect.Effect<number>
strictEqual(v, 42)
const v2 = yield* fn({ a: 7 }) as Effect.Effect<number>
strictEqual(v2, 7)
}))
})

describe("Effect.fnUntraced", () => {
Expand Down
Loading