递归执行promise nodejs

Posted

技术标签:

【中文标题】递归执行promise nodejs【英文标题】:execute promises recursively nodejs 【发布时间】:2015-11-14 02:50:06 【问题描述】:

以下函数通过 xmlrpc 在我的服务器上创建新文件夹

var createFolder = function(folder_name) 
  var defer = Q.defer();
  client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) 
    if (err) 
      if (err.responseString && err.responseString.match('already exist')) 
        //call the same function recursively with folder_name+Math.round(Math.random()*100)
       else 
        defer.reject(err);
      
     else 
      defer.resolve(folder_name);
    
  );
  return defer.promise;

函数成功创建新文件夹 但是,如果文件夹已经存在,我想用新的文件夹名称再次递归地触发这个函数,然后在 promise 中返回它,这样无论何时调用这个函数,它都会返回文件夹名称,不管它执行了多少次

类似

createFolder('directory').then(function(resp)
 console.log(resp);// may return directory || directory1 .... etc
);

**编辑** 所以我设法通过传递延迟对象来实现这一点 让我知道是否有更优雅的方法来实现这一目标

var createFolder = function(folder_name,defer) 
  defer =defer ||  Q.defer();
  client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) 
    if (err) 
      if (err.responseString && err.responseString.match('already exist')) 
        return createFolder(folder_name+Math.round(Math.random()*100,defer)
       else 
        defer.reject(err);
      
     else 
      defer.resolve(folder_name);
    
  );
  return defer.promise;

【问题讨论】:

return createFolder(...);? 我设法通过传递延迟对象来实现这一目标” - 一个可怕的想法。这样做会更好defer.resolve(createFolder(folder_name+Math.floor(Math.random()*100))); 谢谢,我会尝试重构代码并遵循您的方法 【参考方案1】:

这是一个不好的简单的解决问题的方法:

var createFolder = function(folder_name) 
  var defer = Q.defer();
  client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) 
    if (err) 
      if (err.responseString && err.responseString.match('already exist')) 
        //call the same function recursively with folder_name+Math.round(Math.random()*100)
        defer.resolve(createFolder(folder_name+Math.round(Math.random()*100)));
       else 
        defer.reject(err);
      
     else 
      defer.resolve(folder_name);
    
  );
  return defer.promise;

但是,defer 被认为是不好的做法。这是very nice article about promises。

你应该喜欢这样的东西:

var createFolder = function(folder_name) 
  return Q.Promise(function(resolve, reject)
     client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) 
        if (err) 
          if (err.responseString && err.responseString.match('already exist')) 
            //call the same function recursively with folder_name+Math.round(Math.random()*100)
            resolve(createFolder(folder_name+Math.round(Math.random()*100)));
           else 
            reject(err);
          
         else 
          resolve(folder_name);
        
      );
  );

编辑:正如@Bergi 所指出的,这仍然不正确且难以调试。methodCall 回调引发的任何潜在错误实际上都不会拒绝承诺,而且很可能被吞下(尽管这个回调似乎很少出错,但它可能会演变)。请参考his answer 了解更好的方法。

另外,请参阅the official Q doc here。

【讨论】:

在您将 return 修复为 resolve() 后,我确实撤回了我的反对意见,但这些解决方案仍然不应该受到青睐。 @Bergi,你能解释一下原因吗? @AlexisMétaireau:一方面,OP 正在寻找一种优雅的方式 :-) 如今几乎不考虑延期。甚至有人可能会争辩说,将它们与 promise 调用一起使用类似于 deferred antipattern。最后,还有一些小细节,例如回调中的抛出安全性,例如当err.responseString 没有match 方法时。 @Bergi 我同意你的看法。这正是我提出使用 Promise 模式的第二个版本的原因。我保留了第一个,因为我不是来告诉 OP 他应该如何编码,而是帮助他解决问题。关于匹配的事情,我们谈论的是 node.js,所以我认为这不是问题。 @Bergi,我明白你的意思。在methodCall 的回调中抛出的任何错误都将被吞噬。【参考方案2】:

永远不要在普通(非承诺)回调中执行任何逻辑。在最低级别承诺:

var defer = Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) 
  if (err) defer.reject(err);
  else defer.resolve(folder_name);
);
return defer.promise;

或者使用Q.ninvoke 更简单:

return Q.ninvoke(client, 'methodCall', 'create_folder', [sessionID, folder_name]);

现在我们可以开始实现我们的递归了。使用then 回调非常简单,您可以从中返回另一个承诺。在你的情况下:

function createFolder(folder_name) 
  return Q.ninvoke(client, 'methodCall', 'create_folder', [sessionID, folder_name])
    .catch(function(err) 
      if (err.responseString && err.responseString.match('already exist')) 
        return createFolder(folder_name+Math.floor(Math.random()*100));
       else 
        throw err;
      
    );

【讨论】:

从我从 OP 的代码中看到的,我不认为 methodCall 抛出任何东西。因此,我认为这段代码不起作用。 @QuentinRoy:好吧,如果出现错误,承诺将被拒绝 - 它不一定是 thrown 异常。我们捕捉到那些表明已经存在文件夹的错误,然后再试一次。 是的,但据我所知,错误是作为提供给methodCall 的回调的第一个参数给出的(我不能说我喜欢这个API,但它似乎是它的工作原理这里)。结果 catch 不会被调用。这也不例外。 @QuentinRoy:是的,这只是 nodejs 回调(“nodeback”)约定。如果第一个参数给出错误,则promise 拒绝并且catch 回调 被调用

以上是关于递归执行promise nodejs的主要内容,如果未能解决你的问题,请参考以下文章

基于递归 Promise 的目录读取

将递归异步函数转换为 Promise

使用Promise表行走/递归

如何使用 Promise Kit 调用递归函数?

无法通过递归调用node.js中的函数从promises获得响应

js利用递归与promise 按顺序请求数据