Generator function does not yield proper value

3.6k Views Asked by At

I have recently learned a bit of ES6 and started my new project using koa.js running on io.js.

In the code below, I am trying to check if there is already another item with the same url slug.

But the value of counter.next().value always returns a function and hence the function _slugExists always returns true

'use strict';

let _ = require('lodash');
let config = require('../../config');

let monk = require('monk');
let wrap = require('co-monk');
let db = monk(config.db);

let _apps = wrap(db.get('apps'));

function _validateApp(app) {
  let slug = app.slug.trim();

  if (!app.name.trim()) {
    throw new Error('App name was not specified.');
  }

  if (!slug) {
    throw new Error('App URL was not specified.');
  }

  if (_slugExists(slug)) {
    throw new Error('Another app with the same URL already exists.');
  }
}

function* _count(filter) {
  yield _apps.count(filter);
}

function _slugExists(slug) {
  let counter = _count({
    slug: slug
  });

  return counter.next().value !== 0;
}

module.exports = {
  list: function*(next) {
    this.status = 200;
    this.body = yield _apps.find({});
  },
  create: function*(next) {
    try {
      let app = this.request.body;
      _validateApp(app);

      this.status = 201;
      this.body = {
        id: yield _apps.insert({
          name: app.name.trim(),
          slug: app.slug.trim(),
          created_at: new Date()
        })
      };
    } catch (error) {
      console.log(`[ERROR] ${error.message}`);

      this.status = 500;
      this.body = {
        error: error.message
      };
    }
  }
}
1

There are 1 best solutions below

0
On BEST ANSWER

In koa, which is based on co, any async operation must yield promises all the way up to koa. You can also yield generators, but not iterators. Particularly important is to make sure nested async operations are not left hanging:

function* middleware(next) {
  yield Promise.resolve(0); // Yielding a promise. Good.
  yield (function() { return Promise.resolve(0); })(); // Also yielding a promise. Good.

  yield gen(4); // Yielding iterator. NOT GOOD!
  yield gen; // Yielding generator. Good, but no arg.
  yield* gen(4); // Delegating iterator. Good!

  hangingNested(); // Not yielding anything, async is lost. NOT GOOD!
  yield properNested; // Yielding generator with nested delegate, good!
}

function* gen(arg) {
  yield Promise.resolve(1);
  yield Promise.resolve(2);
  yield Promise.resolve(3);
  return arg;
}

function hangingNested() { // not a generator, nothing is yielded outside.
  gen(4); // iterator is lost.
}

function* properNested() {
  yield* gen(4); // Delegating iterator.
}

With that in mind, there are various ways you could fix your code, for example:

function* _validateApp(app) {
  let slug = app.slug.trim();

  if (!app.name.trim()) {
    throw new Error('App name was not specified.');
  }

  if (!slug) {
    throw new Error('App URL was not specified.');
  }

  if (yield* _slugExists(slug)) {
    throw new Error('Another app with the same URL already exists.');
  }
}

function* _count(filter) {
  return yield _apps.count(filter);
}

function* _slugExists(slug) {
  let counter = yield* _count({
    slug: slug
  });

  return counter !== 0;
}

module.exports = {
  list: function*(next) {
    this.status = 200;
    this.body = yield _apps.find({});
  },
  create: function*(next) {
    try {
      let app = this.request.body;
      yield* _validateApp(app);

      this.status = 201;
      this.body = {
        id: yield _apps.insert({
          name: app.name.trim(),
          slug: app.slug.trim(),
          created_at: new Date()
        })
      };
    } catch (error) {
      console.log(`[ERROR] ${error.message}`);

      this.status = 500;
      this.body = {
        error: error.message
      };
    }
  }
}