Turning nested jQuery Promises into flat code structure using .done() and .then() This code uses jQuery promises in combination with callbacks does the following:


Saves Group UUID to server using storeGroupOnServer()
Creates a DOM element for the group using createGroupDomNode()
Creates child elements for each image, using createNewChildDomNode()


How can this be written more flat (avoiding the nesting), using .then()?

var groupPromise = groupInstance.promise();
var arrayOfFiles = groupInstance.files();

groupPromise.done(function(fileGroupInfo) {
    storeGroupOnServer(fileGroupInfo.uuid, function(groupid){

        createGroupDomNode(groupid, function(groupnode){

            $.each(arrayOfFiles, function(i, file) {
                file.done(function(fileInfo) {
                    createNewChildDomNode(fileInfo.cdnUrl, groupnode);
                });
            });
        });
    });
});


Step 1: storeGroupOnServer():

storeGroupOnServer = function(uuid, callback) {
    $.post('saveGroup.php', {uuid:uuid},
    function(data) {
        callback(data.groupid);
    },'json');
};


Step 2: createGroupDomNode():

createGroupDomNode = function(groupid, callback) {
    var cloner = $('#cloner');
    var newnode = cloner.clone(true);
    newnode.attr('id',groupid);
    newnode.removeClass('hide');
    cloner.after(newnode);
    callback(newnode);
}


Step 3: createNewChildDomNode():

function createNewChildDomNode(imgpath, groupdom){
    imgnode = $('&lt;img/&gt;').attr('src',imgpath);
    picnode = $('&lt;picture/&gt;').append(imgnode);
    $(groupdom).first().prepend(picnode);
}
You can substitute returning Promise objects from functions for using callbacks as parameters to function, to chain .then() and .catch(). Array.prototype.map() and Promise.all() can be substituted for jQuery.each() which does not await the fulfillment of Promise objects iterated.

Which requires returning the jQuery promise object from storeGroupOnServer

storeGroupOnServer = function(uuid, callback) {
    return $.post('saveGroup.php', {uuid:uuid},'json')
           .then(function(data) {
             return data.groupid;
           });
};


and returning a jQuery promise object of Promise from createGroupDomNode

createGroupDomNode = function(groupid, callback) {
    var cloner = $('#cloner');
    var newnode = cloner.clone(true);
    newnode.attr('id',groupid);
    newnode.removeClass('hide');
    cloner.after(newnode);
    return $.Deferred().resolve(newnode);
}


and awaiting &lt;img&gt; element load event 

function createNewChildDomNode(imgpath, groupdom){
    return $.Deferred(function(dfd) {
             imgnode = $('&lt;img/&gt;')
                       .on("load", dfd.resolve)
                       .on("error", dfd.reject)
                       .attr('src',imgpath);
             picnode = $('&lt;picture/&gt;').append(imgnode);
             $(groupdom).first().prepend(picnode);
           }).promise();
}


For example



const data = {
  uuid: {
    1: 4,
    2: 5,
    3: 6
  }
};
const response = {
  4: "a",
  5: "b",
  6: "c"
};

const groupPromise = Promise.resolve(data.uuid[1]); // initial `Promise`
const arrayOfFiles = [Promise.resolve({
  fileInfo: {
    cdnUrl: 10
  }
}), Promise.resolve({
  fileInfo: {
    cdnUrl: 20
  }
}), Promise.resolve({
  fileInfo: {
    cdnUrl: 30
  }
})];

const storeGroupOnServer = (uuid) =&gt; {
  console.log({
    uuid
  });
  // `return` response `Promise`
  return Promise.resolve(response[uuid])
};

const createGroupDomNode = (groupid) =&gt; {
  console.log({
    groupid
  });
  // `return` `groupid`
  return Promise.resolve(groupid)
};
const createNewChildDomNode = (groupdom, {
  // destructure `cdnUrl` define as `imgpath`
  fileInfo: {
    cdnUrl: imgpath 
  }
}) =&gt; {
  console.log({
    imgpath,
    groupdom
  });
  // process `&lt;img&gt;` elements here
  // `return` resolved or rejected `Promise` 
  // at `load` or `error` event
  return Promise.resolve("done processing " + imgpath)
};
let handleFiles = (groupnode, file) =&gt; {
  console.log({
    groupnode,
    file
  });
  // handle each `file` `Promise`, pass `group` node to 
  // function passed to `.then()`
  return file.then(createNewChildDomNode.bind(null, groupnode))
};
const handleGroupDomNodes = (groupnode) =&gt; {
  console.log({
    groupnode
  });
  // use `Promise.all()`, `.map()` to process array of `Promise` objects
  return Promise.all(
    arrayOfFiles.map(handleFiles.bind(null, groupnode))
  )
};

groupPromise
.then(storeGroupOnServer)
.then(createGroupDomNode)
.then(handleGroupDomNodes)
.then(complete =&gt; console.log(complete)) // to illustrate chain completes
.catch(err =&gt; console.error(err)); // handle errors in chain