【问题标题】:Cascading promises级联承诺
【发布时间】:2015-02-18 14:42:21
【问题描述】:

除了简单的承诺之外的任何事情通常都会让我感到困惑。在这种情况下,我需要对 N 个对象连续执行 2 次异步调用。首先,我需要从磁盘加载一个文件,然后将该文件上传到邮件服务器。我更喜欢一起做这两个动作,但我已经通过首先完成所有读取和所有上传来让它工作。下面的代码有效,但我不禁认为它可以做得更好。我不明白的一件事是为什么 when.all 不拒绝。我对文档的解释似乎暗示如果其中一个承诺拒绝,.all 将拒绝。为了测试错误,我已经注释掉了较低的解析。没有错误,一切似乎都可以正常工作并且有意义。

mail_sendOne({
  from: 'greg@', 
  to: 'wilma@', 
  subject: 'F&B data', 
  attachments: [
    {name: 'fred.html', path: '/fred.html'}, 
    {name: 'barney.html', path: '/barney.html'}
  ]
})
.done(
  function(res) {
    console.log(res)
  },
  function(err) {
    console.log('error ', err);
  }
)  

function mail_sendOne(kwargs) {
  var d = when.defer();
  var promises = [], uploadIDs = [], errs = [];

  // loop through each attachment
  for (var f=0,att; f < kwargs.attachments.length; f++) {
    att = kwargs.attachments[f];

    // read the attachment from disk  
    promises.push(readFile(att.path)
    .then(
      function(content) {
        // upload attachment to mail server
        return uploadAttachment({file: att.name, content: content})
        .then(
          function(id) {
            // get back file ID from mail server
            uploadIDs.push(id)
          },
          function(err) {
            errs.push(err)
          }
        )
      },
      function(err) {
        errs.push(err)
      }
    ))
  }

  // why doesn't this reject?
  when.all(promises)
  .then(
    function(res) {
      if (errs.length == 0) {
        kwargs.attachments = uploadIDs.join(';');
        sendEmail(kwargs)
        .done(
          function(res) {
            d.resolve(res);
          },
          function(err) {
            d.reject(err);
          }      
        )
      }
      else {
        d.reject(errs.join(','))
      }
    }
  )

  return d.promise;
}

function readFile(path) {
  var d = when.defer();
  var files = {
    '/fred.html': 'Fred Content',
    '/barney.html': 'Barney Content'
  }

  setTimeout(function() {
    d.reject('Read error');
    //d.resolve(files[path]);
  }, 10);

  return d.promise;
}

function uploadAttachment(obj) {
  var d = when.defer();

  setTimeout(function() {
    d.reject('Upload error');
    //d.resolve(new Date().valueOf());
  }, 10);

  return d.promise;
}

function sendEmail(kwargs) {
  var d = when.defer();

  setTimeout(function(){
    console.log('sending ', kwargs)
  }, 5);

  return d.promise;
}

【问题讨论】:

  • 你能指出代码中你认为应该导致它拒绝的那一行吗?
  • 当我在较低的函数中执行 d.reject 时,我“假设”会导致 when.all 被拒绝。
  • 啊,是的。起初我错过了那部分。我认为when.all 未被拒绝的原因是您通过将错误处理程序回调传递给then 来捕获所有错误。

标签: javascript node.js promise when-js


【解决方案1】:

那里有几个反模式使代码比它需要的更混乱。一种是在您应该链接它们时在.then 回调中使用.then。另一个是deferred antipattern

首先,让我们分别创建一个用于读取和上传的函数,这两个函数都处理各自的错误并抛出一个带有更多上下文的新错误:

function readAndHandle(att) {
    return readFile(att.path)
        .catch(function (error) {
            throw new Error("Error encountered when reading " + att.path + error);
        });
}

function uploadAndHandle(att, content) {
    return uploadAttachment({file: att.name, content: content})
        .catch(function (error) {
            throw new Error("Error encountered when uploading " + att.path + error);
        });
}

然后,让我们将这两个组合成一个函数,首先读取文件,然后上传文件。这个函数返回一个承诺:

// returns a promise for an uploaded file ID
function readAndUpload(att) {
    return readAndHandle(att)
        .then(function (content) {
             return uploadAndHandle(att, content);
        });
}

现在您可以使用 .map() 将附件数组映射到文件 ID 的承诺数组:

var uploadedIdsPromise = kwargs.attachments.map(readAndUploadAsync);

这就是您可以传递给when.all() 的内容。 .then 处理程序会将一组 ID 传递给它的回调:

return when.all(uploadedIdsPromise)
    .then(function (ids) {
        kwargs.attachments = ids.join(";");
        return sendEmail(kwargs);
    })
    .catch(function (error) {
        // handle error
    });

这就是它的要点。

这里要注意的一件大事是,除了修改kwargs 变量的一个地方之外,promise 不会修改promise 链之外的任何内容。这有助于保持逻辑简洁和模块化。

The Deferred Antipattern

请注意,上面的代码中没有d.resolves 或d.rejects。唯一应该使用deferreds 的时候是当你还没有可用的承诺时(或在其他一些特殊情况下)。即便如此,如今仍有一些首选的方式来创建承诺。

when.js API 文档是这样说的:

注意:不鼓励使用 when.defer。在大多数情况下,使用 when.promise 、 when.try 或 when.lift 可以更好地分离关注点。

目前推荐的从一些非承诺异步 API 创建承诺的方法是使用revealing constructor pattern。以您的 uploadAttachment() 函数为例,它看起来像这样:

function uploadAttachment(obj) {
  return when.promise(function (resolve, reject) {
      setTimeout(function() {
          resolve(new Date().valueOf());
          // or reject("Upload error");
      }, 10);
  });
}

这就是 ES6 Promise API 的工作方式,它使您不必在 deferred 对象周围随机播放。

【讨论】:

  • 很好的答案,谢谢。我不能说我完全理解了,但我会玩它的。
猜你喜欢
  • 1970-01-01
  • 2017-10-24
  • 1970-01-01
  • 1970-01-01
  • 2018-12-06
  • 2015-03-14
  • 1970-01-01
  • 1970-01-01
  • 2015-10-06
相关资源
最近更新 更多