javascriptgeneratorkoaco

Can't find an explanation for koa.js workshop solution, exercise number one


It has to be rather simple, but I can't understand the solution for the beginning exercise from koa workshop.

The test:

var co = require('co');
var assert = require('assert');
var fs = require('./index.js');

describe('.stats()', function () {
  it('should stat this file', co(function* () {
    var stats = yield fs.stat(__filename);
    assert.ok(stats.size);
  }));
});

The solution and the task:

var fs = require('fs');

/**
 * Create a yieldable version of `fs.stat()`:
 *
 *   app.use(function* () {
 *     var stats = yield exports.stat(__filename);
 *   })
 *
 * Hint: you can return a yieldable.
 */

exports.stat = function (filename) {
    return function (done) {
        fs.stat(filename, done);
    }
};

The way I think of this test is: co library runs the generator function for us, the fs.stat(__filename) invokes, returns the

function (done) {
    fs.stat(filename, done);
}

Then, all I have are questions: why does anonymous function returns fs.stat() at the same place and where does it take done callback? I have logged this callback out, it's generators next() method with stats object as a passing parameter, but I can't find any information about callbacks injection in co. How does this work? Thank you in advance.


Solution

  • I wasn't able to find this information on the main README.md, but it looks like co provides a callback to thunks automatically. So in the example above co provides the done callback and invokes the:

    function (done) {
        fs.stat(filename, done);
    }
    

    If there is an error this callback expects that the main function would return error (done(err);), if everything is ok: done(null, result);. After that the result is passed to generator. Here is the done callback code:

    function () {
      if (called) return;
      called = true;
      next.apply(ctx, arguments);
    }
    

    Well, let's return to the solution. The co's git README.md says:

    Thunk support only remains for backwards compatibility and may be removed in future versions of co.

    So let's rewrite it to the modern view with promises:

    exports.stat = function(filename) {
      return new Promise((resolve, reject) => {
        fs.stat(filename, (err, stats) => {
          if (err) {reject(err);}
          resolve(stats);
        });
      });
    };
    

    We wrap the promise with the anonymous function which get the filename value and encapsulates it for the returning promise object, which is one of the supported yieldables in co. This promise starts the fs.stat with the callback. This callback resolves the promise if everything is ok, or rejects it otherwise. The resolved result returns into the generator.