Cascading promises

Anything that goes beyond a simple promise usually puzzles me. In this case, I need to make 2 asynchronous calls in a string on N number of objects. First, I need to upload the file from disk, and then upload this file to the mail server. I prefer to do two actions together, but I got his job by completing all the first reads and all the orders in the second. The code below works, but I cannot help but think that this can be done better. One thing that I don't understand is why when.all does not reject. My interpretation of the documents seems to imply that if one of the promises rejects, then .all rejects. I commented on lower resolutions to check for errors. Without errors, everything looks fine and makes sense.

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; } 
+6
source share
1 answer

There are a couple of antipatterns that make the code messier than necessary. One uses .then inside a .then callback when you need to bind them. The other is a delayed antipattern .

First, create one function for reading and unloading, both of which process the corresponding error and cause a new error with some context:

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

Then, combine the two into a function that first reads the file and then loads it. This function returns a promise:

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

Now you can use .map() to map an array of attachments to an array of promises for file identifiers:

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

And this is what you can pass to when.all() . The .then handler on this passes an array of identifiers to its callback:

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

and that is pretty much the essence of it.

It is worth noting here that with the exception of one place that modifies the kwargs variable, promises does not change anything outside the promise chain. This helps maintain consistency in cleanliness and modularity.

Deferred antipattern

Note that in the above code there is no d.resolve or d.reject . The only time you ever use deferred is when you don't have any promises available yet (or in some other special situations). And even then there are preferred ways to create promises these days.

API documents. API APIs. say the following:

Note. Using when.defer is not recommended. In most cases, using when.promise, when.try, or when.lift provides a better separation of problems.

The current recommended way to create promises from some do not promise asynchronous APIs is to use constructor template expansion . To take your uploadAttachment() function as an example, it would look like this:

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

This is how the ES6 API works, and it eliminates the need to shuffle around a deferred object.

+6
source

Source: https://habr.com/ru/post/979931/


All Articles