UNB/ CS/ David Bremner/ teaching/ cs2613/ books/ mdn/ Reference/ Operators/ yield*

The yield* operator is used to delegate to another iterable object, such as a Generator.

Syntax

yield* expression

Parameters

Return value

Returns the value returned by that iterator when it's closed (when done is true).

Description

The yield* expression iterates over the operand and yields each value returned by it. It delegates iteration of the current generator to an underlying iterator — which we will refer to as "generator" and "iterator", respectively. yield* first gets the iterator from the operand by calling the latter's @@iterator method. Then, each time the next() method of the generator is called, yield* calls the iterator's next() method, passing the argument received by the generator's next() method (always undefined for the first call), and yielding the same result object as what's returned from the iterator's next() method. If the iterator result has done: true, then the yield* expression stops executing and returns the value of that result.

The yield* operator forwards the current generator's throw() and return() methods to the underlying iterator as well. If the current generator is prematurely closed through one of these methods, the underlying iterator will be notified. If the generator's throw()/return() method is called, the throw()/return() method of the underlying iterator is called with the same argument. The return value of throw()/return() is handled like the next() method's result, and if the method throws, the exception is propagated from the yield* expression.

If the underlying iterator doesn't have a throw() method, this causes yield* to throw a TypeError – but before throwing the error, the underlying iterator's return() method is called if one exists. If the underlying iterator doesn't have a return() method, the yield* expression turns into a return statement, just like calling return() on a suspended yield expression.

Examples

Delegating to another generator

In following code, values yielded by g1() are returned from next() calls just like those which are yielded by g2().

function* g1() {
  yield 2;
  yield 3;
  yield 4;
}

function* g2() {
  yield 1;
  yield* g1();
  yield 5;
}

const gen = g2();

console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: 4, done: false}
console.log(gen.next()); // {value: 5, done: false}
console.log(gen.next()); // {value: undefined, done: true}

Other Iterable objects

Besides generator objects, yield* can also yield other kinds of iterables (e.g., arrays, strings, or arguments objects).

function* g3(...args) {
  yield* [1, 2];
  yield* "34";
  yield* args;
}

const gen = g3(5, 6);

console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: "3", done: false}
console.log(gen.next()); // {value: "4", done: false}
console.log(gen.next()); // {value: 5, done: false}
console.log(gen.next()); // {value: 6, done: false}
console.log(gen.next()); // {value: undefined, done: true}

The value of yield* expression itself

yield* is an expression, not a statement, so it evaluates to a value.

function* g4() {
  yield* [1, 2, 3];
  return "foo";
}

function* g5() {
  const g4ReturnValue = yield* g4();
  console.log(g4ReturnValue); // 'foo'
  return g4ReturnValue;
}

const gen = g5();

console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false} done is false because g5 generator isn't finished, only g4
console.log(gen.next()); // {value: 'foo', done: true}

Method forwarding

The next(), throw(), and return() methods of the current generator are all forwarded to the underlying iterator.

const iterable = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next(v) {
        console.log("next called with", v);
        count++;
        return { value: count, done: false };
      },
      return(v) {
        console.log("return called with", v);
        return { value: "iterable return value", done: true };
      },
      throw(v) {
        console.log("throw called with", v);
        return { value: "iterable thrown value", done: true };
      },
    };
  },
};

function* gf() {
  yield* iterable;
  return "gf return value";
}

const gen = gf();
console.log(gen.next(10));
// next called with undefined; the argument of the first next() call is always ignored
// { value: 1, done: false }
console.log(gen.next(20));
// next called with 20
// { value: 2, done: false }
console.log(gen.return(30));
// return called with 30
// { value: 'iterable return value', done: true }
console.log(gen.next(40));
// { value: undefined, done: true }; gen is already closed

const gen2 = gf();
console.log(gen2.next(10));
// next called with undefined
// { value: 1, done: false }
console.log(gen2.throw(50));
// throw called with 50
// { value: 'gf return value', done: true }
console.log(gen.next(60));
// { value: undefined, done: true }; gen is already closed

If the return()/throw() method of the underlying iterator returns done: false, the current generator continues executing and yield* continues to delegate to the underlying iterator.

const iterable = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next(v) {
        console.log("next called with", v);
        count++;
        return { value: count, done: false };
      },
      return(v) {
        console.log("return called with", v);
        return { value: "iterable return value", done: false };
      },
    };
  },
};

function* gf() {
  yield* iterable;
  return "gf return value";
}

const gen = gf();
console.log(gen.next(10));
// next called with undefined
// { value: 1, done: false }
console.log(gen.return(20));
// return called with 20
// { value: 'iterable return value', done: false }
console.log(gen.next(30));
// { value: 2, done: false }; gen is not closed

If the underlying iterator doesn't have a throw() method and the generator's throw() is called, yield* throws an error.

const iterable = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next(v) {
        count++;
        return { value: count, done: false };
      },
    };
  },
};

function* gf() {
  yield* iterable;
  return "gf return value";
}

const gen = gf();
gen.next(); // First next() starts the yield* expression
gen.throw(20); // TypeError: The iterator does not provide a 'throw' method.

Specifications

Browser compatibility

See also