Skip to content

Commit b1d38ee

Browse files
rbucktonsandersn
andauthored
Fix early call to return/throw on generator (#186)
* Fix early call to return/throw on generator * Additional minor documentation updates * Update docs/generator.md Co-authored-by: Nathan Shively-Sanders <[email protected]> Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 4e27e9f commit b1d38ee

File tree

3 files changed

+43
-20
lines changed

3 files changed

+43
-20
lines changed

docs/generator.md

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ Here's the body of the `__generator` helper:
77

88
```js
99
__generator = function (thisArg, body) {
10-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t;
11-
return { next: verb(0), "throw": verb(1), "return": verb(2) };
10+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
11+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
1212
function verb(n) { return function (v) { return step([n, v]); }; }
1313
function step(op) {
1414
if (f) throw new TypeError("Generator is already executing.");
15-
while (_) try {
16-
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
17-
if (y = 0, t) op = [0, t.value];
15+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
16+
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;
17+
if (y = 0, t) op = [op[0] & 2, t.value];
1818
switch (op[0]) {
1919
case 0: case 1: t = op; break;
2020
case 4: _.label++; return { value: op[1], done: false };
@@ -98,7 +98,7 @@ arguments, and their purpose:
9898
| 7 (endfinally) | | Exits a finally block, resuming any previous operation (such as a break, return, throw, etc.) |
9999

100100
# State
101-
The `_`, `f`, `y`, and `t` variables make up the persistent state of the `__generator` function. Each variable
101+
The `_`, `f`, `y`, `t`, and `g` variables make up the persistent state of the `__generator` function. Each variable
102102
has a specific purpose, as described in the following sections:
103103

104104
## The `_` variable
@@ -148,6 +148,12 @@ The `t` variable is a temporary variable that stores one of the following values
148148

149149
> NOTE: None of the above cases overlap.
150150
151+
## The `g` variable
152+
The `g` variable is a temporary variable that holds onto the generator object for the purpose of attaching a
153+
`Symbol.iterator` method (if its available), and holds onto that value until the generator is started, allowing
154+
155+
it to also act as the [`suspendedStart`](https://tc39.es/ecma262/#table-internal-slots-of-generator-instances) state.
156+
151157
# Protected Regions
152158
A **Protected Region** is a region within the `body` function that indicates a
153159
`try..catch..finally` statement. It consists of a 4-tuple that contains 4 labels:
@@ -164,13 +170,18 @@ The final step of the `__generator` helper is the allocation of an object that i
164170
`Generator` protocol, to be used by the `__awaiter` helper:
165171

166172
```ts
167-
return { next: verb(0), "throw": verb(1), "return": verb(2) };
173+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) },
174+
typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }),
175+
g;
168176
function verb(n) { return function (v) { return step([n, v]); }; }
169177
```
170178

171179
This object translates calls to `next`, `throw`, and `return` to the appropriate Opcodes and
172180
invokes the `step` orchestration function to continue execution. The `throw` and `return` method
173-
names are quoted to better support ES3.
181+
names are quoted to better support ES3. In addition, a `Symbol.iterator` method is added to the
182+
generator if the global `Symbol` constructor is available. Once we return, the object reference in
183+
the `g` variable isn't used by the main [orchestration method](#orchestration), so we can use its
184+
truthiness as a mechanism to determine whether the generator is in the `suspendedStart` state.
174185

175186
# Orchestration
176187
The `step` function is the main orechestration mechanism for the `__generator` helper. It
@@ -181,9 +192,9 @@ Here's a closer look at the `step` function:
181192
```ts
182193
function step(op) {
183194
if (f) throw new TypeError("Generator is already executing.");
184-
while (_) try {
185-
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
186-
if (y = 0, t) op = [0, t.value];
195+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
196+
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;
197+
if (y = 0, t) op = [op[0] & 2, t.value];
187198
switch (op[0]) {
188199
case 0: case 1: t = op; break;
189200
case 4: _.label++; return { value: op[1], done: false };
@@ -219,13 +230,25 @@ The main body of the `step` function consists of a `while` loop which continues
219230
instructions until the generator exits or is suspended:
220231

221232
```ts
222-
while (_) try ...
233+
while (g && (g = 0, op[0] && (_ = 0)), _) try ...
223234
```
224235

236+
During the first call to `next()`, `return()`, or `throw()`, the generator will be in the `suspendedStart` state. This
237+
is indicated by the `g` variable being "truthy" since it still holds a reference to the generator object. If `g` is
238+
"truthy", then we reset it and check whether the first instruction sent to the generator (`op[0]`) is either a
239+
`return` (`op[0] === 1`) or `throw` (`op[0] === 2`) Opcode, indicating an abrupt completion.
240+
241+
If the first instruction is abrupt, we can emulate [GeneratorResumeAbrupt](https://tc39.es/ecma262/#sec-generatorresumeabrupt)
242+
by setting the state variable (`_`) to a falsy value, which will skip the loop body and
243+
[complete the generator](#handling-a-completed-generator).
244+
245+
If this is _not_ the first instruction sent to the generator, or if the first instruction was `next()`, we will proceed
246+
to evaluate the loop body.
247+
225248
When the generator has run to completion, the `_` state variable will be cleared, forcing the loop
226249
to exit.
227250

228-
## Evaluating the generator body.
251+
## Evaluating the generator body
229252
```ts
230253
try {
231254
...
@@ -272,8 +295,8 @@ reduce the overall footprint of the helper.
272295
The first two statements of the `try..finally` statement handle delegation for `yield*`:
273296

274297
```ts
275-
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
276-
if (y = 0, t) op = [0, t.value];
298+
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;
299+
if (y = 0, t) op = [op[0] & 2, t.value];
277300
```
278301

279302
If the `y` variable is set, and `y` has a `next`, `throw`, or `return` method (depending on the
@@ -282,7 +305,7 @@ current operation), we invoke this method and store the return value (an Iterato
282305
If `t` indicates it is a yielded value (e.g. `t.done === false`), we return `t` to the caller.
283306
If `t` indicates it is a returned value (e.g. `t.done === true`), we mark the operation with the
284307
`next` Opcode, and the returned value.
285-
If `y` did not have the appropriate method, or `t` was a returned value, we reset `y` to a falsey
308+
If `y` did not have the appropriate method, or `t` was a returned value, we reset `y` to a falsy
286309
value and continue processing the operation.
287310

288311
## Handling operations
@@ -402,7 +425,7 @@ if (!(t = ...) && (op[0] === 6 || op[0] === 2)) {
402425
```
403426

404427
If we encounter an Opcode 6 ("catch") or Opcode 2 ("return"), and we are not in a protected region,
405-
then this operation completes the generator by setting the `_` variable to a falsey value. The
428+
then this operation completes the generator by setting the `_` variable to a falsy value. The
406429
`continue` statement resumes execution at the top of the `while` statement, which will exit the loop
407430
so that we continue execution at the statement following the loop.
408431

@@ -463,7 +486,7 @@ current **protected region** from the stack and spin the `while` statement to ev
463486
operation again in the next **protected region** or at the function boundary.
464487

465488
## Handling a completed generator
466-
Once the generator has completed, the `_` state variable will be falsey. As a result, the `while`
489+
Once the generator has completed, the `_` state variable will be falsy. As a result, the `while`
467490
loop will terminate and hand control off to the final statement of the orchestration function,
468491
which deals with how a completed generator is evaluated:
469492

tslib.es6.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function __generator(thisArg, body) {
8383
function verb(n) { return function (v) { return step([n, v]); }; }
8484
function step(op) {
8585
if (f) throw new TypeError("Generator is already executing.");
86-
while (_) try {
86+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
8787
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;
8888
if (y = 0, t) op = [op[0] & 2, t.value];
8989
switch (op[0]) {

tslib.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ var __createBinding;
125125
function verb(n) { return function (v) { return step([n, v]); }; }
126126
function step(op) {
127127
if (f) throw new TypeError("Generator is already executing.");
128-
while (_) try {
128+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
129129
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;
130130
if (y = 0, t) op = [op[0] & 2, t.value];
131131
switch (op[0]) {

0 commit comments

Comments
 (0)