user341554
user341554

Reputation: 599

Multiple nested AJAX requests with arrays

I'm using Axios / promises to make AJAX requests, but the structure is a bit confusing. Basically I want this to happen:

  1. Get masterlist.xml.
  2. Parse the master list to get an array of category list URLs.
  3. Get each category.xml.
  4. Parse each category list to get an array of manifest URLs.
  5. Get each manifest.xml and parse data.

Example structure:

masterlist.xml

<master>
  <category>action</category>
  <category>romance</category>
</master>

Category XMLs (e.g. action.xml and romance.xml)

<manifestUrls>
  <manifest>foo/bar/manifest.xml</manifest>
  <manifest>alice/bob/manifest.xml</manifest>
  <manifest>hello/world/manifest.xml</manifest>
</manifestUrls>

Manifest XMLs

<manifest>
  <data>Blah blah blah</data>
<manifest>

I'm a bit stuck on how to structure this as an Axios request with then. I'd like to have three functions, getMaster(), getCategory(url), and getManifest(url) that are preferably independent (e.g. getMaster doesn't have to call getCategory directly), but it seems like it might be necessary.

How would this be structured in Axios?

Upvotes: 1

Views: 288

Answers (1)

JLRishe
JLRishe

Reputation: 101738

One of the main benefits of promises is that they allow you to easily avoid interdependence between your methods.

Here is a rough outline of how you could do this.

// put it all together
getMaster()
    .then(parseMaster)
    .then(function (categories) {
        return Promise.all(categories.map(getAndParseCategory));
    })
    .then(flatten)  // the previous then() resolves to an array-of-arrays
    .then(function (manifestUrls) {
        return Promise.all(manifestUrls.map(getManifest));
    })
    .then(function (manifests) {
        // manifests is an array of all manifests
    });


// Examples of what each of the functions used above would do
function getMaster() {
    return axios.get('masterUrl')
        .then(function (response) { return response.data; });
}

function parseMaster(masterContent) {
    // parse and return an array of categories
}

function getCategory(name) {
    var url = // ... build the URL based on the name

    return axios.get(url)
        .then(function (response) { return response.data; });
}

function parseCategory(categoryContent) {
    // parse and return an array of URLs synchronously for one category
}

function getAndParseCategory(name) {
    return getCategory(name).then(parseCategory);
}

function getManifest(url) {
    return axios.get(url)
        .then(function (response) { return response.data; });
}

function flatten(arrayOfArrays) {
    return [].concat.apply([], arrayOfArrays);
}

If you're using Bluebird or something else that gives promises a .map() method, then you can tidy that pipeline up a bit:

// using Promise.resolve() at the beginning to ensure 
// the chain is based of the desired kind of promise
Promise.resolve()
    .then(getMaster)
    .then(parseMaster)
    .map(getCategory)
    .map(parseCategory)
    .then(flatten)       // previous line resolves to an array-of-arrays
    .map(getManifest)
    .then(function (manifests) {
        // manifests is an array of all manifests
    });

Of course, you could also define your own .map method if you don't want to import a whole third party promise library:

if (!Promise.prototype.map) {
    Promise.prototype.map = function (func) {
        return this.then(function (result) {
            return Promise.all(result.map(func));
        });
    };
}

Edit: To respond to your question in the comments below. If you wanted to pass the category text along so that it could be included in the manifest URLs, I think a clean way to do this would be to include that in the data returned from getCategory() so that parseCategory can make use of it. Everything else could stay the same.

Example:

function getCategory(name) {
    var url = // ... build the URL based on the name

    return axios.get(url)
        .then(function (response) { 
            return {
                name: name,
                data: response.data 
            };
        });
}

function parseCategory(categoryContent) {
    var urls = // parse URLs from categoryContent.data

    return urls.map(function (url) {
        return categoryContent.name + '/' + url;
    });
}

Upvotes: 3

Related Questions