Synchronous code with functions

- 1 answer

Ad

The following functions allow synchronous blocks of code to be created with a sleep function, and without blocking.

function synchronous(generator)
{
   var _generator = generator();
   function done()
   {
      var result = _generator.next().value;
      if (result instanceof Promise)
         result.then(done);
   }
   done();
}

function sleep(ms)
{ 
   return new Promise(function(res, rej) { setTimeout(res, ms); });
}


// Runs without blocking, as you expect
synchronous(function* ()
{
   console.log('a');
   yield sleep(2000);
   console.log('b');
});

The following code however, will call functions c() and d() at the same time, instead of waiting for c() to finish before calling d().

synchronous(function* ()
{
   Log('a');
   yield sleep(2000);
   Log('b');
   yield sleep(2000);

   synchronous(c);
   synchronous(d);

   function* c()
   {
      Log('c');
      yield sleep(2000);
      Log('d');
      yield sleep(2000);
   }

   function* d()
   {
      Log('e');
      yield sleep(2000);
      Log('f');
      yield sleep(2000);
   }
});

Prints a b c e d f

How can I resolve this and have d() start after c() finishes, without setting up a far more complex system of promises?

Ad

Answer

Ad

Quick solution

First of all, you can't make an asynchronous function synchronous. Asynchronity is poisonous, meaning all code calling asynchronous code and expecting to run sequentially has to be asynchronous itself. In your case the outer function is already asynchronous, so you can make it work by using yield*:

synchronous(function* ()
{
   Log('a');
   yield sleep(2000);
   Log('b');
   yield sleep(2000);

   yield* c();
   yield* d();

   function* c()
   {
      Log('c');
      yield sleep(2000);
      Log('d');
      yield sleep(2000);
   }

   function* d()
   {
      Log('e');
      yield sleep(2000);
      Log('f');
      yield sleep(2000);
   }
});

Good solution

Your synchronous function can be replaced by babelHelpers.asyncToGenerator:

function asyncToGenerator(fn) {
  return function () {
    var gen = fn.apply(this, arguments);
    return new Promise(function (resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          Promise.resolve(value).then(function (value) {
            step("next", value);
          }, function (err) {
            step("throw", err);
          });
        }
      }
      step("next");
    });
  };
}

This function is used to wrap a function at definition (and not at invocation), like this:

var a = asyncToGenerator(function*() {
    yield sleep(100);
});
var b = asyncToGenerator(function*() {
    var c = asyncToGenerator(function*() {
        yield sleep(300);
    });
    var d = asyncToGenerator(function*() {
        yield sleep(300);
    });

    yield a();
    yield sleep(200);
    yield c();
    yield d();
});

b(); // start the whole process with b
Ad
source: stackoverflow.com
Ad