Mastering yield* in Koa: How Generators and co Simplify Async Flow

This article demystifies the use of yield* in Koa middleware by comparing plain yield with delegating yield, explaining how the co library handles generators and promises, and outlining the practical advantages of native yield* syntax for clarity, performance, and correct this binding.

Node Underground
Node Underground
Node Underground
Mastering yield* in Koa: How Generators and co Simplify Async Flow

While exploring Koa middleware implementations, the pattern yield* next often appears and can be confusing. Below is a concise study of how yield and yield* work in JavaScript generators and how the co library leverages them.

Delegating yield

First, consider a normal generator:

function* outer() {
  yield 'begin';
  yield inner();
  yield 'end';
}
function* inner() {
  yield 'inner';
}
var it = outer(), v;
v = it.next().value; console.log(v); // -> begin
v = it.next().value; console.log(v); // -> {}
v = it.next().value; console.log(v); // -> end

Now the delegating version using yield*:

function* outer() {
  yield 'begin';
  var rt = yield* inner();
  console.log(rt); // -> return from inner
  yield 'end';
}
function* inner() {
  yield 'inner';
  return 'return from inner';
}
var it = outer(), v;
v = it.next().value; console.log(v); // -> begin
v = it.next().value; console.log(v); // -> inner
v = it.next().value; console.log(v); // -> end

According to the specification, yield* takes an iterable object, iterates over it, and its own expression value becomes the iterator's return value when done: true. In practice, yield* is used to "execute" another generator inside a generator and retrieve its return value.

yield and co

The co library allows yielding promises, thunks, arrays, objects, and even generator functions directly:

co(function* () {
  var a = yield Promise.resolve(1);          // -> 1
  var b = yield later(10);                  // -> 10
  var c = yield fn;                         // -> fn_1
  var d = yield fn(5);                      // -> fn_5
  var e = yield [Promise.resolve('a'), later('b'), fn, fn(5)]; // -> ['a','b','fn_1','fn_5']
  var f = yield {a: Promise.resolve('a'), b: later('b'), c: fn, d: fn(5)}; // -> {a:'a',b:'b',c:'fn_1',d:'fn_5'}
  function* fn(n) { n = n || 1; var a = yield later(n); return 'fn_' + a; }
  function later(n, t) { t = t || 1000; return function(done){ setTimeout(function(){ done(null, n); }, t); }; }
}).catch(function(e){ console.error(e); });

Co detects generator functions or generator objects and wraps them, effectively turning yield fn or yield fn() into yield co(fn) or yield co(fn()). The library finally returns a Promise that resolves with the yielded value.

Purpose of yield*

In a pure co environment, yield* fn is almost equivalent to yield co(fn). Nevertheless, using native yield* has three concrete benefits:

Eliminate the extra abstraction layer of co, gaining a slight performance boost.

Make the programmer’s intent explicit, avoiding confusion between plain yield and delegating yield*.

Preserve the correct this binding when invoking methods on objects.

For example, yielding a thunk without knowing its type:

var v = yield later();

Without inspecting later, you cannot be sure what is returned. Using yield* signals that later must return a generator:

var v = yield* later();

A common pitfall with co is the loss of this context:

function Runner(){ this.name = 'runner'; }
Runner.prototype.run = function* (t) { var r = yield later(this.name, t); return 'run->' + r; };
var runner = new Runner();
var result = yield runner.run; // result is 'run->undefined'

Co internally calls the generator with its own context, breaking the original this. Using native syntax fixes the issue:

var result = yield* runner.run(); // result is 'run->runner'

In summary, yield* provides a clearer, more performant, and safer way to compose generators compared to relying on co 's hidden magic.

For further reading, see the original analysis at purplebamboo.github.io .

(Unless otherwise noted, this article is licensed under CC BY‑NC‑ND 4.0.)

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptAsyncKoaGeneratorsYield
Node Underground
Written by

Node Underground

No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.