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
dseminara 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