Skip to content

Commit a8f8c0e

Browse files
authored
fix: Handle non-awaited async generator (#4417)
1 parent 4844d4b commit a8f8c0e

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

internal/runtime/runtime.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,15 +418,23 @@ func Source(unsupportedJSFeatures compat.JSFeature) logger.Source {
418418
var resume = (k, v, yes, no) => {
419419
try {
420420
var x = generator[k](v), isAwait = (v = x.value) instanceof __await, done = x.done
421-
Promise.resolve(isAwait ? v[0] : v)
421+
return q = Promise.resolve(isAwait ? v[0] : v)
422422
.then(y => isAwait
423423
? resume(k === 'return' ? k : 'next', v[1] ? { done: y.done, value: y.value } : y, yes, no)
424424
: yes({ value: y, done }))
425425
.catch(e => resume('throw', e, yes, no))
426426
} catch (e) {
427+
q = Promise.resolve()
427428
no(e)
428429
}
429-
}, method = k => it[k] = x => new Promise((yes, no) => resume(k, x, yes, no)), it = {}
430+
}, method = (k, call, wait, clear) => it[k] = x => (
431+
call = new Promise((yes, no, run) => (
432+
run = () => resume(k, x, yes, no),
433+
queue ? queue.then(run) : run())),
434+
clear = () => queue === wait && (queue = void 0),
435+
queue = wait = call.then(clear, clear),
436+
call
437+
), q, queue, it = {}
430438
return generator = generator.apply(__this, __arguments),
431439
it[__knownSymbol('asyncIterator')] = () => it,
432440
method('next'),

scripts/end-to-end-tests.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,127 @@ for (const flags of [[], ['--target=es6', '--target=es2017', '--supported:async-
748748
}
749749
`,
750750
}, { async: true }),
751+
test(['in.js', '--outfile=node.js', '--format=esm'].concat(flags), {
752+
'in.js': `
753+
async function* i() {
754+
yield 1
755+
yield 2
756+
}
757+
async function* f() {
758+
yield* i()
759+
}
760+
export let async = async () => {
761+
let it, stateA, stateB
762+
it = f()
763+
stateA = it.next()
764+
stateB = it.next()
765+
766+
stateA = await stateA
767+
stateB = await stateB
768+
769+
if (stateA.done !== false || stateA.value !== 1) throw 'fail: f: next A'
770+
if (stateB.done !== false || stateB.value !== 2) throw 'fail: f: next B'
771+
772+
stateA = await it.next()
773+
if (stateA.done !== true || stateA.value !== void 0) throw 'fail: f: done'
774+
}
775+
`,
776+
}, { async: true }),
777+
test(['in.js', '--outfile=node.js', '--format=esm'].concat(flags), {
778+
'in.js': `
779+
async function* i() {
780+
yield 1
781+
yield 2
782+
}
783+
async function* f() {
784+
for await (const x of i())
785+
yield x;
786+
}
787+
export let async = async () => {
788+
let it, stateA, stateB
789+
it = f()
790+
stateA = it.next()
791+
stateB = it.next()
792+
793+
stateA = await stateA
794+
stateB = await stateB
795+
796+
if (stateA.done !== false || stateA.value !== 1) throw 'fail: f: next A'
797+
if (stateB.done !== false || stateB.value !== 2) throw 'fail: f: next B'
798+
799+
stateA = await it.next()
800+
if (stateA.done !== true || stateA.value !== void 0) throw 'fail: f: done'
801+
}
802+
`,
803+
}, { async: true }),
804+
test(['in.js', '--outfile=node.js', '--format=esm'].concat(flags), {
805+
'in.js': `
806+
async function* f() {
807+
await Promise.resolve()
808+
return yield 1
809+
}
810+
export let async = async () => {
811+
let it, stateA, stateB
812+
it = f()
813+
stateA = it.next()
814+
stateB = it.next(2)
815+
816+
stateA = await stateA
817+
stateB = await stateB
818+
819+
if (stateA.done !== false || stateA.value !== 1) throw 'fail: f: next'
820+
if (stateB.done !== true || stateB.value !== 2) throw 'fail: f: done'
821+
}
822+
`,
823+
}, { async: true }),
824+
test(['in.js', '--outfile=node.js', '--format=esm'].concat(flags), {
825+
'in.js': `
826+
async function* f(order) {
827+
order.push('before')
828+
await Promise.resolve()
829+
return yield 1
830+
}
831+
export let async = async () => {
832+
let it, stateA, stateB, order = []
833+
it = f(order)
834+
stateA = it.next()
835+
stateB = it.next(2)
836+
order.push('after')
837+
838+
stateA = await stateA
839+
stateB = await stateB
840+
841+
if (order[0] !== 'before' || order[1] !== 'after') throw 'fail: f: order: ' + JSON.stringify(order)
842+
if (stateA.done !== false || stateA.value !== 1) throw 'fail: f: next'
843+
if (stateB.done !== true || stateB.value !== 2) throw 'fail: f: done'
844+
}
845+
`,
846+
}, { async: true }),
847+
test(['in.js', '--outfile=node.js', '--format=esm'].concat(flags), {
848+
'in.js': `
849+
async function* f(order) {
850+
yield 0
851+
order.push('before')
852+
await Promise.resolve()
853+
return yield 1
854+
}
855+
export let async = async () => {
856+
let it, stateA, stateB, order = []
857+
it = f(order)
858+
await it.next()
859+
stateA = it.next()
860+
order.push('after')
861+
stateB = it.next(2)
862+
863+
stateA = await stateA
864+
stateB = await stateB
865+
866+
if (order[0] !== 'before' || order[1] !== 'after') throw 'fail: f: order: ' + JSON.stringify(order)
867+
if (stateA.done !== false || stateA.value !== 1) throw 'fail: f: next'
868+
if (stateB.done !== true || stateB.value !== 2) throw 'fail: f: done'
869+
}
870+
`,
871+
}, { async: true }),
751872
test(['in.js', '--outfile=node.js', '--format=esm'].concat(flags), {
752873
'in.js': `
753874
async function* f() {

0 commit comments

Comments
 (0)