0

Although I kind of figured out how the Koa flow mechanims work (I think), I can't seem to grasp all of the differences between co and co.wrap. This is the code that is giving the unexpected behavior:

"use strict";
var co = require("co");

function ValidationError(message, obj, schema) {
    Error.call(this, "Validation failed with message \"" + message + "\".");
    this.name = "ValidationError";
    this.object = obj;
    this.schema = schema;
}

ValidationError.prototype = Object.create(Error.prototype);

function ValidatorWithSchema(properties, schema) {
    this.properties = properties;
    this.schema = schema;
}

ValidatorWithSchema.prototype.validate = function* (obj) {
    var validatedObj = obj;
    for (let schemaKey in this.schema) {
        validatedObj = yield this.properties[schemaKey](validatedObj, this.schema[schemaKey]);
    }
    return validatedObj;
};

var typeGen = function* (obj, type) {
    console.log("Checking against "+ type.name);
    var primitives = new Map([
        [String, "string"],
        [Number, "number"],
        [Boolean, "boolean"]
    ]);
    if (!((obj instanceof type) || (primitives.has(type) && (typeof obj === primitives.get(type))))) {
        var error = new ValidationError("Given object is not of type " + type.name, obj);
        throw error;
    }
    return obj;
};

var validator = new ValidatorWithSchema({type: typeGen}, {type: String});
var runWrap = r => {
    console.log(r);
    console.log("### WRAP ###");
    var validate = co.wrap(validator.validate);
    validate(11).then(console.log, console.error);

};
co(function* () {
    yield validator.validate(11);
}).then(runWrap, runWrap);

The output to this code follows:

Checking against String
{ [ValidationError] name: 'ValidationError', object: 11, schema: undefined }
### WRAP ###
11

You can see that I wrapped the use of co.wrap in order for it to be subsequent to the simple co use. Now it is evident that typeGen is not getting called in the second attempt, but why is this the case? Shouldn't the two outcomes be identical?

Michele De Pascalis
  • 932
  • 2
  • 9
  • 26
  • I wonder why `typeGen` is a generator? And why are you using generators+co here at all, there doesn't seem to be anything asynchronous? Or do you plan something to be? – Bergi Sep 20 '15 at 21:31
  • The code posted above is a flattened module/test couple that replicated the puzzling behavior. – Michele De Pascalis Sep 20 '15 at 21:45
  • Yeah, the puzzling behaviour is easily explained (see my answer below), but I think `typeGen` should be a promise-returning function not a generator-returning one. – Bergi Sep 20 '15 at 21:49
  • I have yet to become that familiar with promises, so I am relying on yielding generators to achieve composition for the moment. Is the case of this post the only reason why promises should be superior? I mean, I see thunks are really hard to compose maintaining linearity, but generators seem to be not. – Michele De Pascalis Sep 20 '15 at 21:56
  • Generators that are wrapped are just a fill-in for proper `async/await` syntax (coming in ES7), where such async functions always return promises. Therefore this behaviour should be the default, and you should be wrapping all your generator functions immediately so that all your methods return promises. – Bergi Sep 20 '15 at 21:59
  • And yes, promises compose as easily (or even better than) generators; e.g. via `Promise.all`. – Bergi Sep 21 '15 at 10:08

1 Answers1

1

This is just the very common problem of calling "unbound" methods.
You will still need to call the wrapped function as a method on your validator instance. You can for example use call on the validate function:

var validate = co.wrap(validator.validate);
validate.call(validator, 11).then(console.log, console.error);

Alternatively, you'd need to .bind() the method:

var validate = co.wrap(validator.validate.bind(validator));
validate(11).then(console.log, console.error);

or better yet, just wrap the generator function at the point of its definition, so that the method always returns a promise right away:

ValidatorWithSchema.prototype.validate = co.wrap(function* (obj) {
    …
});

…
validator.validate(11).then(console.log, console.error);
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • How could I not see it! I had read about it just yesterday, but I guess I'm too used to languages where partially applied instance methods hold a reference of the instance they belong to. Thanks! – Michele De Pascalis Sep 20 '15 at 21:49