StopIteration, ForwardToTarget, ... & symbols

Brendan Eich brendan at mozilla.org
Tue Nov 27 00:45:16 PST 2012


Allen Wirfs-Brock wrote:
> There would be another way to accomplish supporting generator return values that doesn't require dynamically constructing new StopIteration instances and avoids all Realm issues:  Capture the return value in the generator instance when it enters the "closed" state and then throw the single immutable StopIteration object (could be a Symbol).  A client of the generator instance that catches StopIteration and expects a return value can directly query the generator instance (via a property access) to get that return value.  It accomplishes the same thing but doesn't require multiple StopIteration instances, an extract allocation on generator returns, a call to isStopIteration to test, etc.

This is not the PEP-380 path we've championed, but let's talk about it. 
Say the generator-iterator has a .value property that contains the 
return value. Any for-of iteration will suppress the value, so no 
difference there. A task.js (http://taskjs.org) style scheduler, on the 
other hand, will have to do something like this (diff from 
ES6-as-proposed task.js indicated with -/+ lines):

function runScheduledTask(task) {
     var result = task.result, send = (task.runState === R_RESOLVED);
     try {
         task.runState = R_RUNNING;
         task.result = void 0;
         if (task.threadState === T_CANCELLED) {
             task.thread.close();
             task.result = void 0;
             task.runState = R_RESOLVED;
             task.threadState = T_CLOSED;
         } else {
             var p = (send ? task.thread.send(result) : 
task.thread["throw"](result)) || READY;
             task.runState = R_BLOCKED;
             p.then(function(value) {
                 task.result = value;
                 task.runState = R_RESOLVED;
                 if (task.threadState === T_STARTED) {
                     task.scheduler.schedule(task);
                     pump(task.scheduler);
                 }
             }, function(e) {
                 task.result = e;
                 task.runState = R_REJECTED;
                 if (task.threadState === T_STARTED) {
                     task.scheduler.schedule(task);
                     pump(task.scheduler);
                 }
             });
         }
     } catch (e) {
         task.threadState = T_CLOSED;
         if (e instanceof StopIteration) {
-           task.result = e.value;
+          task.result = task.thread.value;
             task.runState = R_RESOLVED;
             task.deferred.resolve(e.value);
         } else {
             task.result = e;
             task.runState = R_REJECTED;
             task.deferred.reject(e);
         }
     }
}

Not much of a change, but something crucial is lost. The try could 
possibly .send or .next another task's generator-iterator (task.thread), 
possibly under pump if it immediately dispatched (it doesn't, but 
suppose it did). You're programming by side effects here, requiring the 
catcher of e to know that task.thread.value is the mutable pigeon-hole 
to inspect.

This smells pretty bad compared to the PEP-380 style alternative, which 
cleanly localizes the result to e.value.

/be


More information about the es-discuss mailing list