级联的承诺

除了一个简单的承诺之外,任何事情都会让我感到困惑。 在这种情况下,我需要在N个对象上连续执行2个asynchronous调用。 首先,我需要从磁盘加载文件,然后将该file upload到邮件服务器。 我更喜欢一起做这两个动作,但是我已经通过首先完成所有的读取操作并将所有的上传次要做到了。 下面的代码工作,但我不禁认为它可以做得更好。 有一件事我不明白的是为什么when.all不拒绝。 我对文件的解释似乎意味着,如果其中一个承诺被拒绝,那么所有的人都会拒绝。 为了testing错误,我已经提出了较低的解决scheme。 没有错误的事情似乎工作正常,是有道理的。

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

那里有几个反模式使得代码变得比需要的更复杂。 一个人正在使用.then在回.then时候,你应该链接它们。 另一个是延迟反模式 。

首先,我们为读取和上传创build一个函数,这两个函数分别处理各自的错误,并用更多的上下文引发新的错误:

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

然后,让我们将这两者结合成一个首先读取文件的函数,然后上传它。 这个函数返回一个promise:

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

现在,您可以使用.map()将附件数组映射到文件标识的一组promise:

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

这就是你可以传入when.all() 。 这个.then处理程序会传递一个ID数组到它的callback函数中:

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

这几乎是它的要点。

这里需要注意的一点是,除了修改kwargsvariables的地方之外,承诺不会修改承诺链以外的任何内容。 这有助于保持逻辑清洁和模块化。

延迟反模式

请注意,在上面的代码中没有d.resolved.reject 。 唯一的一次你应该使用deferred s是当你还没有一个可用的承诺(或在其他一些特殊情况下)。 而即使如此,现在也有一些首选的方式来创造承诺。

when.js API Docs这样说:

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

目前推荐的从一些非承诺asynchronousAPI创build承诺的方法是使用揭示构造函数模式 。 以你的uploadAttachment()函数为例,它看起来像这样:

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

这是ES6承诺API的工作方式,它可以避免不必要deferred对象。