0

I have a javascript function that generates an email.

If there are attachments to the email, I'm loopinp through the attachments and I upload each time one attchments to Dropbox and instead of actually attaching the attachment I append to the body of the email a Dropbox link to the file.

(The relevant code is wrapped with backslashes)

self.email = function (action, contact) {
    var emailTo = contact.email();
    var attachment = "";
    var lineBreak = "%0D%0A";
    var signature = uvm.user().fullName() 
    var body;
    var dropboxLink = "";
    switch (action) {
        case "a":
            body = "aaaa";
            break;
        case "b": case "c":
            body "bbbccc";
            break;
        //////////The piece of code that I'm working on//////////
        default:
            $.each(self.checkedDocs(), function (key, doc) {
                self.service.getDropboxLink(doc, function (result) {
                    dropboxLink = result;
                    attachment += lineBreak + doc.documentDescription() + ": " + dropboxLink; 
                });
            });
        body = "Please click on the link(s) below to view your document(s): "
        //////////End//////////
         }
    attachment = attachment ? attachment + lineBreak + lineBreak : lineBreak;
    body += lineBreak + attachment + signature;
    window.location.href = "mailto:" + emailTo + "?subject=" + self.subject() + "&body=" + body;
}

And this is the getDropboxLink function:

self.getDropboxLink = function (doc, callback) {
    $.ajax({
        url: "/API/dropbox/DropboxUpload",
        type: "POST",
        data: ko.toJSON(doc),
        contentType: "application/json",
        success: function (data) {
            callback(data);
        }
    });
}

This code is not working properly, if the case is default and there are emails attachments, the email is generaed before I get the Dropbox link from the API.

I can add async=false to the POST request and that will solve the issue, but async=false is deprecated.

I think I should do it with a callback or a promise but it's complicated becasue the part of the code that generates the email belongs to all the cases of the switch and the call to the API is only on the default part.

If action is default I need:

attachment = attachment ? attachment + lineBreak + lineBreak : lineBreak;
    body += lineBreak + attachment + signature;
    window.location.href = "mailto:" + emailTo + "?subject=" + self.subject() + "&body=" + body;

to occur after this is done:

$.each(self.checkedDocs(), function (key, doc) {
            self.service.getDropboxLink(doc, function (result) {
                dropboxLink = result;
                attachment += lineBreak + doc.documentDescription() + ": " + dropboxLink; 
            });
        });

if the action is a or b or c it doesn't matter.

I hope I'm clear. Any suggestion?

Any help would be very much appreciated.

user3378165
  • 6,546
  • 17
  • 62
  • 101
  • async stuff is virual, once you've got one single async request in your code, the whole call chain must be async as well. That is, you function should be like `getDropBoxLinks().then(formatEmail).then(sendEmail)` – georg Dec 13 '16 at 12:06
  • related: http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call – georg Dec 13 '16 at 12:07
  • Thank you, I would love to do it with a promise, would you be able to guide me a little bit more with it? – user3378165 Dec 13 '16 at 15:59

2 Answers2

1

You could solve the asynchronous issue by keeping a counter of how many asynchronous replies you still need to get, and only when that reaches zero you would continue to generate the output. If you put the output generating code in a (nested) function, you can call it both synchronously (for the non-default cases) and asynchronously (for the default case, when the counter is zero):

self.email = function (action, contact) {
    var lineBreak = "%0D%0A";

    function openEmail(body, attachment) {
        attachment = attachment ? attachment + lineBreak + lineBreak : lineBreak;
        body += lineBreak + attachment + uvm.user().fullName();
        window.location.href = "mailto:" + contact.email() + "?subject=" + self.subject() + "&body=" + body;
    }

    switch (action) {
        case "a":
            openEmail("aaaa");
            break;
        case "b": case "c":
            openEmail("bbbccc");
            break;
        default:
            var attachment = "", 
                docs = self.checkedDocs(),
                count = docs.length;
            if (!count) { // treat the case where there are no attachments
                openEmail("No attachments");
                break;
            }
            $.each(docs, function (key, doc) {
                self.service.getDropboxLink(doc, function (dropboxLink) {
                    attachment += lineBreak + doc.documentDescription() + ": " + dropboxLink; 
                    if (--count == 0) { // last one:
                        openEmail("Please click on the link(s) below to view your document(s): ", attachment);
                    }
                });
            });
    }
}

Promises

You could look into promises which deal nicely with asynchronous tasks. You would first alter the function getDropboxLink so it would return the return value of the $.ajax() call, which is a jQuery promise. The callback is no longer necessary, nor the success handler.

Then in the email function you would call the then method on each of these promises to return the formatted attachment text. The then method also returns a promise, and all these promises could be mapped into an array. With $.when you can then get the signal when all these promises have been fulfilled. The arguments you get in its callback will be whatever you returned in the then callbacks.

Here is the code that has these adjustments:

self.email = function (action, contact) {
    var lineBreak = "%0D%0A";

    function openEmail(body, attachment) {
        attachment = attachment ? attachment + lineBreak + lineBreak : lineBreak;
        body += lineBreak + attachment + uvm.user().fullName();
        window.location.href = "mailto:" + contact.email() + "?subject=" + self.subject() + "&body=" + body;
    }

    switch (action) {
        case "a":
            openEmail("aaaa");
            break;
        case "b": case "c":
            openEmail("bbbccc");
            break;
        default:
            var docs = self.checkedDocs();
            if (!docs.length) { // treat the case where there are no attachments
                openEmail("No attachments");
                break;
            }
            $.when.apply($, docs.map(function (doc) {
                return self.service.getDropboxLink(doc).then(function (dropboxLink) {
                    return lineBreak + doc.documentDescription() + ": " + dropboxLink;                 
                });
            })).done(function() {
                var attachment = [].join.call(arguments, '');
                openEmail("Please click on the link(s) below to view your document(s): ", attachment);
            });
    }
}

self.service.getDropboxLink = function (doc) {
    return $.ajax({
        url: "/API/dropbox/DropboxUpload",
        type: "POST",
        data: ko.toJSON(doc),
        contentType: "application/json"
    });
}

NB: your code references both self.service.getDropboxLink and self.getDropboxLink (without service). Please adjust to your actual situation.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thank you very much, that's working perfectly, I would really want to do it with a promise, but not really sure how to do it, so I'll wait maybe I'll get another answer. Thanks a lot, this is a good, simple and working solution! :) – user3378165 Dec 13 '16 at 16:25
  • Added promise solution – trincot Dec 13 '16 at 18:45
  • Amazing!!! Thank you so much!! You are a real genius!! Working like a charm! Just one question- what is the second `url` parameter in the API call? A mistake? – user3378165 Dec 13 '16 at 21:29
  • You're welcome ;-). Ah, and that url was the url I used for testing. Removed now. – trincot Dec 13 '16 at 21:36
  • I wish I could vote more than once, such a clear, well explained and great answer! Thank you! – user3378165 Dec 13 '16 at 21:38
0

first suggest use promise.all, but I will explain in an old way.

window.location.href = xxx will execute before you get dropbox link, so first you need to know how many async calls, and in every done callback, check if all tasks are done

var results = arr.map((x,index)=>{
  $.ajax(url,data,function(){
    results[index] = true
    if(results.every((x)=>x)){
      location.href=xxxx
    }
  })
  return false
})
Josh Lin
  • 2,397
  • 11
  • 21