将Promise包装成同步函数

3

我正在编写一个基于Node的CLI,其中同步行为通常比异步更合适,我希望能够利用以下约定:

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

因此,例如--使用 RSVP承诺实现--我编写了以下异步函数来调用shell脚本:
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
            }
        });
    }); 
};

这个可以工作,现在我想让它同步:

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

我认为适用于shellSync的是:

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;
};

很不幸,这只是运行而已,从未返回。我认为,也许可以实现轮询间隔来执行条件语句,替代while循环:
    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 
    }

当然,去掉while循环会导致函数立即返回(但承诺尚未实现)。因此,我尝试将while循环的“等待”功能与轮询频率结合起来实现。

也许这是一个疯狂的想法,但是使用traceur,你可以使用EC6的await关键字。它会将您的代码重新编译成一个奇怪的状态机,但这可能是您情况下相当容易的解决方案。 - David Mulder
这在JS本身内部是不可能的。你需要一个插件来添加这个功能。我不知道这个模块是否工作正常,所以我不能建议使用它,但你可以看一下deasync - t.niese
@t.niese 我确实简要地看了一下...我的第一印象是它可能不能真正做到我需要的。我还发现了一个名为exec-sync的NPM模块,在OSX上运行得非常好(它可以编译到每个平台),但在Ubuntu上似乎会出问题。 :( - ken
2
因为js本身不支持多线程。只要你在while(!shellResults) {}块中,没有其他的js代码会被执行。 - t.niese
@t.niese 啊,那就讲得通了。该死。 - ken
显示剩余2条评论
1个回答

0

如果您想要同步,最简单的方法是在内部代码中使用同步API进行消耗,但您想将异步代码包装为同步,对吧?

我认为可以使用 fibers (https://www.npmjs.org/package/fibers)来实现这一点,更具体地说,是使用 futures 。以下是一个小例子:

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()();

更具体地针对您的代码:


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;
    }
};

编辑提示:您应该将所有内容包装在 (function() {...}).future()(); 中以在 fiber 上运行此代码。


看起来非常有前途。迫不及待想试试,但必须先睡觉。明天一早就会尝试。 :) - ken
使用您的代码示例进行第一次尝试时,调用future.wait()时出现“无法在没有纤程的情况下等待”的错误。将进行调查。 - ken
抱歉,我在分配的时间内无法理解它。这是我的要点:https://gist.github.com/ksnyde/d78cc01f62def105f1a0 - ken
你应该将所有内容包装在 (function() { }).future()(); 中。 - dseminara

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接