将 promise 包装到 Sync 函数中

Posted

技术标签:

【中文标题】将 promise 包装到 Sync 函数中【英文标题】:Wrapping promise into a Sync function 【发布时间】:2014-11-09 10:19:21 【问题描述】:

我正在编写一个节点 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 promise 实现——我编写了以下用于调用 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 关键字。它会将你的代码重新编译成一个奇怪的状态机,但这可能是一个很容易解决你的情况的方法。 这在 JS 本身是不可能的。您需要一个用于在外部添加此功能的方法的插件。不知道这个模块好不好用,不建议用,你可以看看deasync @t.niese 我确实简单地看过它......我的第一印象是它可能无法真正满足我的需求。我还发现了一个名为 exec-sync 的 NPM 模块,它在 OSX 上运行良好(它可以编译到每个平台上),但在 Ubuntu 上似乎失败了。 :( @DavidMulder 我现在很想避免使用 EC6。它在我的清单上,要完全包裹起来,但如果可能的话,我现在想把自己限制在 Node 上(没有 Harmony)。我确实注意到 Task.js 看起来像是 ES6 对这个问题的回答。 因为js 本身不是多线程的。只要您在while(!shellResults) 块中,就不会执行其他js 代码。 【参考方案1】:

如果您希望同步代码,最简单的方法是在您的内部代码上使用同步 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()();在光纤上运行它

【讨论】:

看起来很有希望。迫不及待想试试,但必须先睡觉。会在早上尝试第一件事。 :) 第一次尝试使用您的代码示例我在调用future.wait() 时收到“无法等待没有光纤”的错误。将进行调查。 错误。在分配的时间内无法理解它。这是我的要点:gist.github.com/ksnyde/d78cc01f62def105f1a0 你应该把所有的东西都包裹在 (function() ).future()();

以上是关于将 promise 包装到 Sync 函数中的主要内容,如果未能解决你的问题,请参考以下文章

将对象绑定到 Promise.then() 参数的正确方法 [重复]

如何在 node.js 中将函数与 Promise 同步

将 fetch 包装在 Promise 中有啥好处? [复制]

解决for循环中异步请求顺序不一致的问题

javascript 获取Promise *和*拒绝/解析函数指针。保存一些包装。

实现一个自定义Promise