Skywalker13

Diary of an ex-GeeXboX developer…

Watt generators and the callback hell

Posted at — Apr 9, 2016

$$\text{Watt} = \frac{U^2}{R} = U \times I = I^2 \times R$$

Since ES6 (or ES2015) a great improvement is the generator functions. The callback hell doesn’t exists anymore if you use the right modules like watt.

Example:

function copyFile(source, dest, callback) {
  fs.exists(source, (exists) => {
    if (!exists) {
      callback("Source does not exist");
      return;
    }

    fs.exists(dest, (exists) => {
      if (exists) {
        callback("Dest already exists");
        return;
      }

      fs.readFile(source, (err, data) => {
        if (err) {
          callback(err);
          return;
        }

        fs.writeFile(dest, data, callback);
      });
    });
  });
}

copyFile("./src", "./dest", (err) => {
  if (err) {
    console.error(err);
  } else {
    console.log("file copied");
  }
});

This code can be very pretty with the watt generators.

const copyFile = watt (function * (source, dest, next) {
  if (!yield fs.exists (source, next.arg (0))) {
    throw 'Source does not exists';
  }

  if (yield fs.exists (dest, next.arg (0))) {
    throw 'Dest already exists';
  }

  const data = yield fs.readFile (dest, next);
  yield fs.writeFile (dest, data, next);
});

copyFile ('./src', './dest', (err) => {
  if (err) {
    console.error (err);
  } else {
    console.log ('file copied');
  }
});

Note that copyFile is using the callback syntax. That’s the reason why watt is great. It’s works directly with the usual functions with callback.

The next function is not necessary when you yield an other watt generator.

const copier = watt(function* () {
  try {
    yield copyFile("./src1", "./dest1");
    yield copyFile("./src2", "./dest2");
    yield copyFile("./src3", "./dest3");
    console.log("all files copied");
  } catch (ex) {
    console.error(ex);
  } finally {
    console.log("end");
  }
});

copier();

Then it’s possible to use the native for loop.

const copier = watt(function* (files) {
  try {
    for (const file of files) {
      yield copyFile(file.src, file.dest);
    }
    console.log("all files copied");
  } catch (ex) {
    console.error(ex);
  } finally {
    console.log("end");
  }
});

const files = [
  { src: "./src1", dest: "./dest1" },
  { src: "./src2", dest: "./dest2" },
  { src: "./src3", dest: "./dest3" },
];

copier(files);

And what about the class ? No problem, you can wrap all methods with watt.

Ensure that you are using watt >= 3.2.2

class MyClass {
  constructor() {
    watt.wrapAll(this);
  }

  *m1() {
    yield copier(/* ... */);
    if (yield this.m2("./blabla")) {
      /* ... */
    }
  }

  *m2(dest, next) {
    return yield fs.exists(dest, next.arg(0));
  }
}

const myClass = new MyClass();
myClass.m2("./file").then((exists) => {
  console.log("the file exists");
});

And note here that a Promise is returned if the callback is omitted.

That’s so great, no?

Please, look more examples and documentation on the official README.