AsyncContext:
yield*
Nicolò Ribaudo (Igalia)
September 2025
Recap: AsyncContext and generators
Principle: other code you interact to cannot change your context.
2
function *fn() {
// context 1
yield 1;
// context 1
yield 2;
// context 1
}
let it = fn(); // context 1
// context 2
it.next();
// context 2
// context 3
it.next();
// context 3
Sometimes you want the opposite behavior
And that's what you get with manually written iterators!
next() {
// context from the caller
}
3
// context 2
it.next();
// context 2
// context 3
it.next();
// context 3
Escape hatch? A wrapper
4
const fn = withNextContext(function* () {
// context 1
const [nextCallerSnapshot, nextValue] = yield 1;
// context 1
nextCallerSnapshot.run(() => {
// context 2
})
});
let it = fn(); // context 1
it.next();
// context 2
it.next();
// context 2
Doesn't work with class methods, no hoisting, no function name
Current yield behavior: pseudocode
5
function *fn() {
yield 1;
yield 2;
}
Current yield* behavior: pseudocode
6
function *fn() {
yield* innerIt;
}
Idea: keep the context only for the generator own body
7
function *fn() {
yield* innerIt;
}
let it = fn(); it.next();
it.next();
The context from here is propagated to innerIt.next()
How does this solve the problem?
8
Use case: reading a variable from the .next() caller
9
const ambientAbortSignal = ...;
function* longRunningTask() {
let val = yield 1;
const mergedSignal = ???;
}
I want to combine the generator's ambient abort signal with the one from the .next() caller. How do I do it?
Use case: reading a variable from the .next() caller
10
const ambientAbortSignal = ...;
function* longRunningTask() {
let [nextSignal, val] = yield* readingVariable(ambientAbortSignal, 1);
const mergedSignal = AbortSignal.any([ambientAbortSignal.get(), nextSignal]);
}
function readVariable(asyncVar, yieldValue) {
let done = false;
return { next(v) {
if (!done) {
done = true;
return { value: yieldValue, done: false };
}
return { value: [v, asyncVar.get()], done: true };
} };
}
Use case: run a callback in the previous .next() context
11
function* mapper(cb) {
let res;
while (true) {
res = cb(yield res);
}
}
???
Use case: running a callback in the .next() context
12
function* mapper(cb) {
let res;
while (true) {
res = yield* callInContext(res, value => cb(value));
}
}
function callInContext(yieldValue, callback) {
let done = false;
return { next(v) {
if (!done) {
done = true;
return { value: yieldValue, done: false };
}
return { value: callback(v), done: true };
} };
}