Edge
Edge

Reputation: 2540

Synchronous code with functions

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?

Upvotes: 3

Views: 751

Answers (2)

acjay
acjay

Reputation: 36511

synchronous will suspend its own execution, but not that of its parent stack frame. That's what separates generators from generalized coroutines. If you want to suspend execution of the outer function mid-block, it must itself be a generator and yield to its own parent stack frame. There's no escape hatch that will allow you to defer to the event loop and resume the same code block.

You can only pause execution with yield, and you can only yield from within a generator (or async/await, but same overall deal there).

You might check this article for more info.

Upvotes: 2

Tamas Hegedus
Tamas Hegedus

Reputation: 29906

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

Upvotes: 2

Related Questions