Generator function.sent Meta Property

Status

Stage:
2
Author:
Allen Wirfs-Brock (May 13, 2015)
Champion:
HE Shi-Jun (@hax)

The Problem

When the next method is invoked on a generator objects, the value passed as the first argument to next is "sent" to the generator object and becomes available within the body of the generator function as the value of the yield expression that most recently suspended the generator function. This supports two-way communications between the a generator object and its consumer.

However, the first next that a generator's consumer invokes to start a generator object does not correspond to any yield within the body of the generator function. Instead, the first next simply causes execution of the generator function body to begin at the top of the body.

Because there the first next call does not correspond to a yield within the generator function body there is currently no way for the code with the body to access the initial next argument. For example:

function *adder(total=0) {
   let increment=1;
   while (true) {
       switch (request = yield total += increment) {
          case undefined: break;
          case "done": return total;
          default: increment = Number(request);
       }
   }
}

let tally = adder();
tally.next(0.1); // argument will be ignored
tally.next(0.1);
tally.next(0.1);
let last=tally.next("done");
console.log(last.value);  //1.2 instead of 0.3

In the above example, the argument to the next method normally supplies the value to added to a running tally. Except that the increment value supplied to the first next is ignored.

This proposal provides an alternative way to access the next parameter that works on the first and all subsequent invocations of a generator's next method.

The Proposal

A new meta-property: function.sent

Value and Context

The value of function.sent within the body of a Generator Function is the value passed to the generator by the next method that most recently resumed execution of the generator. In particular, referencing function.sent prior to the first evaluation of a yield operator returns the argument value passed by the next call that started evaluation of the GeneratorBody.

function.sent can appear anywhere a YieldExpress would be legal. Referencing function.sent outside of a GeneratorBody is a Syntax Error.

Usage Example

Here is how the above example might be rewritten using function.sent

function *adder(total=0) {
   let increment=1;
   do {
       switch (request = function.sent){
          case undefined: break;
          case "done": return total;
          default: increment = Number(request);
       }
       yield total += increment;
   } while (true)
}

let tally = adder();
tally.next(0.1); // argument no longer ignored
tally.next(0.1);
tally.next(0.1);
let last=tally.next("done");
console.log(last.value);  //0.3

Specification Updates

The following are deltas to the ECMAScript 2015 Language Specification

8.3 Execution Contests

The following row is added to Table 24:

Component Description
LastYieldValue The value of the most recently evaluated YieldExpression

12.3 Left-Hand-Side Expression

Syntax

MemberExpression[Yield]  :
            ...
            MetaProperty[?Yield]
            ...

MetaProperty[Yield]  :
            NewTarget
             [+Yield] FunctionSent

14.4 Generator Function Definitions

Syntax

FunctionSent  :
            function . sent

14.4.14 Evaluation

FunctionSent : function . sent
    1.  Assert: the running execution context is a Generator Context.
    2.  Let genContext be the running execution context.
    3.  Return the value of the LastYieldValue component of genContext .

25.3.3.1 GeneratorStart(generator, generatorBody)

Between lines 3 and 4 of the ES6 algorithm add the following step:

    3.5.  Set the LastYieldValue component of genContext to undefined.

25.3.3.3 GeneratorResume(generator, value)

Between lines 8 and 9 of the ES6 algorithm add the following step:

    8.5.  Set the LastYieldValue component of genContext to value.

25.3.3.5 GeneratorYield(iterNextObj)

Between lines 5 and 6 of the ES6 algorithm add the following step:

    5.5.  Set the LastYieldValue component of genContext to undefined.