Wrapping promise into a Sync function

2.3k Views Asked by At

I'm writing a node CLI where synchronous behaviour is typically more appropriate than async and I'd like to be able to leverage the following convention:

 # Write functional code as an async function which returns a Promise
 function foobar() { ... }
 # Uses async function but blocks on promise fulfillments
 function foobarSync() { ... }

So for instance -- using the RSVP promise implementation -- I have written the following async function for calling shell scripts:

var shell = function (params,options) {
    options = extend({timeout: 4000},options);
    var commandResponse = '';
    var errorMessage ='';
    // resolve with a promise
    return new RSVP.Promise(function(resolve,reject) {
        var spawn = require('child_process').spawn;
        var timeout = setTimeout(function() {
            reject(new Error('Timed out')); // fulfil promise
        }, options.timeout);
        try {
            var shellCommand = spawn(params.shift(),params);
        } catch (err) {
            clearTimeout(timeout);
            reject(err); // fulfil promise
        }
        shellCommand.stdout.setEncoding('utf8');
        shellCommand.stderr.setEncoding('utf8');
        shellCommand.stdout.on('data', function (data) {
            commandResponse = commandResponse + data;
        });
        shellCommand.stderr.on('data', function (data) {
            errorMessage = errorMessage + data;
        });
        shellCommand.on('close', function (code) {
            if(code !== 0) {
                clearTimeout(timeout);
                reject({code:code, message:errorMessage}); // fulfil promise
            } else {
                clearTimeout(timeout);
                resolve(commandResponse); // fulfil promise
            }
        });
    }); 
};

This works, now I want to make synchronously:

 # Works
 shell(['ls','-l']).then( function (results) {
      console.log('Result was: %s', results);
 });
 # Would like to see work
 var results = shellSync(['ls','-l']);

What I thought would work for shellSync is:

var shellSync = function (params,options) {
    options = extend({pollingInterval: 100},options);
    var shellResults = null;
    shell(params,options).then(
        function(results) {
            console.log('Results: %s', results);
            shellResults = results;
            // return results;
        },
        function(err) {
            console.log('Error: %s', err);
            shellResults = err;
            // return err;
        }
    );

    while(!shellResults) {
        // wait until a Promise is returned or broken (and sets the shellResults variable)
    }
    return shellResults;
};

Unfortunately this just runs, never returning. I though that maybe instead of the while loop I'd implement a polling interval to execute the conditional statement on:

    var polling = setInterval(function() {
        // return once shellResults is set; 
        // this setting takes place when either a resolve() or reject() 
        // is called in Promise
        if(shellResults) {
            console.log('results are available');
            clearInterval(polling);
            return shellResults; 
        }
    },options.pollingInterval);

    while(1) {
        // wait 
    }

Of course, removing the while loop results in the function returning immediately (with an as-yet unfulfilled promise). So then I tried to combine the "waiting" functionality of the while loop with a polling frequency implemented

1

There are 1 best solutions below

4
On

The easiest way is to consume sync API on your internal code if you want it to be sync, but you want to wrap the async code as sync, right ?

I think this can be acchieved using fibers (https://www.npmjs.org/package/fibers), or more specifically, futures, a little example

var Future = require("fibers/future");

// async API, will be wrapped by syncFoo
var asyncFoo = function(cb) {
  setTimeout(function() {
    cb("foo");
  }, 1500);
};

var syncFoo = function() {
  var future = new Future();
  console.log("asyncFoo will be called");
  asyncFoo(function(x) {
    future.return(x);
  });
  console.log("asyncFoo ended");
  return future.wait();
};

(function() {
console.log("* Syncfoo will be called");
syncFoo();
console.log("* Syncfoo ended");

console.log("* Syncfoo will be called again");
syncFoo();
console.log("* Syncfoo ended again");

}).future()();

More specific for your code:


var shellSync = function(params, options) {
    var future = new Future();

    options = extend({pollingInterval: 100},options);
    var shellResults = null;
    shell(params,options).then(
        function(results) {
            console.log('Results: %s', results);
            future.return({results: results});
        },
        function(err) {
            console.log('Error: %s', err);
            future.return({err: err});
        }
    );

    var ret = future.wait();
    if (ret.err) {
      throw ret.err;
    } else {
      return ret.results;
    }
};

EDIT NOTE: You should wrap all in (function() {...}).future()(); to run this on a fiber