microsoft/TypeScript · error · TypeError

Generator is already executing.

Error message

Generator is already executing.

What it means

`__generator` downlevels ES6 generators into a state machine and guards against synchronous re-entry: a flag `f` is set while the body is executing, and re-entering `step` while `f` is set throws "Generator is already executing." — matching native generator semantics where synchronous re-entry is illegal.

Source

Thrown at src/compiler/factory/emitHelpers.ts:1125

//                        evaluates any intervening finally blocks between the current
//                        label and the nearest catch block or function boundary. If
//                        uncaught, the exception is thrown to the caller.
//  7: endfinally       - Ends a finally block, resuming the last instruction prior to
//                        entering a finally block.
//
// For examples of how these are used, see the comments in ./transformers/generators.ts
const generatorHelper: UnscopedEmitHelper = {
    name: "typescript:generator",
    importName: "__generator",
    scoped: false,
    priority: 6,
    text: `
            var __generator = (this && this.__generator) || function (thisArg, body) {
                var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
                return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
                function verb(n) { return function (v) { return step([n, v]); }; }
                function step(op) {
                    if (f) throw new TypeError("Generator is already executing.");
                    while (g && (g = 0, op[0] && (_ = 0)), _) try {
                        if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
                        if (y = 0, t) op = [op[0] & 2, t.value];
                        switch (op[0]) {
                            case 0: case 1: t = op; break;
                            case 4: _.label++; return { value: op[1], done: false };
                            case 5: _.label++; y = op[1]; op = [0]; continue;
                            case 7: op = _.ops.pop(); _.trys.pop(); continue;
                            default:
                                if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                                if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                                if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                                if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                                if (t[2]) _.ops.pop();
                                _.trys.pop(); continue;
                        }
                        op = body.call(thisArg, _);
                    } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }

View on GitHub (pinned to b465fdbfe1)

Solutions

  1. Do not synchronously re-enter the generator; defer advancement via setTimeout/queueMicrotask or yield first.
  2. Restructure so the generator yields before any code that could re-advance it runs.
  3. Test generators for re-entrancy when porting callback-style code to generators.

Example fix

// before
function* g() {
  self.next();   // synchronous re-entry -> "Generator is already executing."
  yield 1;
}
const self = g(); self.next();

// after
function* g() {
  queueMicrotask(() => self.next());  // defer
  yield 1;
}
const self = g(); self.next();
Defensive patterns

Strategy: validation

Validate before calling

// Re-entrancy guard for self-driving generators.
let executing = false;
function safeNext(g: Generator, v?: unknown) {
  if (executing) throw new TypeError("Generator is already executing.");
  executing = true;
  try { return g.next(v); } finally { executing = false; }
}

Prevention

When it happens

Trigger: Code inside the generator body synchronously calls .next(), .throw(), or .return() on the same generator instance before the current step has returned.

Common situations: A generator that recursively drives itself; a callback passed out of the generator that re-advances it within the same tick; a thunk that synchronously re-invokes the generator.

Related errors


AI-assisted analysis of microsoft/TypeScript@b465fdbfe1 (2026-08-12). Data as JSON: /api/errors/2c4a25b3b37ef9d3. Report an issue: GitHub.