Search code examples
javascriptaxioses6-promise

How to map an array of objects and fetch network data for each in JS?


I am making apps in React Native. I have to fetch an array of data of categories from a URL and then for each category I have to fetch assets from their respective URLs.

My data is stored in the following format:

From the mainURL:

{
  "id": 1,
  "name": "Home",
  "type": "",
  "url": "",
  "subCategories": [
    {
      "id": 92,
      "name": "Documentary",
      "type": "JTV_LEAF",
      "url": "yyy",
    }
  ]
}

From each category URL,

[
  {
    "id": "1",
    "title": "Inception",
    "type": "vod"
  }
]

How do I fetch data for each category using map and reduce and axios?


This is what I have written so far. I am getting undefined at the end.

export const fetchNavigationFeed = (navUrl, subId) => {
  return dispatch => {
    const url = navUrl
      .replace("__JTV__SUBSCRIBER__ID__", subId);
    dispatch({ type:FETCH_NAVIGATION_FEED });
    return axios.get(url)
      .then(response => {
        let categories = [];
        for (var i = 0; i < response.data.subCategories.length; i++) {
            var cat = response.data.subCategories[i];
            var category = new Category(cat);
            categories.push(category);
        }
        console.log(categories);
        let promises = [];
        categories.map(category => {
          let request = axios.get(category.url)
            .then(assetsJson => {
              let assets = [];
              for (var i = 0; i < assetsJson.data.length; i++) {
                  var ass = assetsJson.data[i];
                  var asset = new Asset(ass);
                  assets.push(asset);
              }
              category.assets = assets;
            });
          promises.push(request);
        });
        axios.all(promises)
          .then(axios.spread(...args) => {
            console.log(args);
          });
        return categories;
      })
      .then(categories => {
        // console.log(categories);
        dispatch({ type:FETCH_NAVIGATION_FEED_SUCCESS, payload:categories });
      });
  }
}

Solution

  • Here is a working example and jest test:


    code.js

    import axios from 'axios';
    
    export const FETCH_NAVIGATION_FEED = 'FETCH_NAVIGATION_FEED';
    export const FETCH_NAVIGATION_FEED_SUCCESS = 'FETCH_NAVIGATION_FEED_SUCCESS';
    
    class Category {
      constructor(json) {
        this.id = json.id;
        this.name = json.name;
        this.url = json.url;
      }
    }
    
    class Asset {
      constructor(json) {
        this.id = json.id;
        this.title = json.title;
      }
    }
    
    export const fetchNavigationFeed = (navUrl, subId) => {
      return async (dispatch) => {
        dispatch({ type: FETCH_NAVIGATION_FEED });
    
        const url = navUrl
          .replace('__JTV__SUBSCRIBER__ID__', subId);
        const response = await axios.get(url);
        const categories = [];
        const promises = [];
        response.data.subCategories.forEach((subCategory) => {
          const category = new Category(subCategory);
          categories.push(category);
          const promise = axios.get(category.url).then((subResponse) => {
            category.assets = [];
            subResponse.data.forEach((asset) => {
              category.assets.push(new Asset(asset));
            });
          });
          promises.push(promise);
        });
        // wait for all the promises simultaneously
        await Promise.all(promises);
    
        dispatch({ type: FETCH_NAVIGATION_FEED_SUCCESS, payload: categories });
      }
    }
    

    code.test.js

    import axios from 'axios';
    import {
      fetchNavigationFeed,
      FETCH_NAVIGATION_FEED,
      FETCH_NAVIGATION_FEED_SUCCESS
    } from './code';
    
    const getMock = jest.spyOn(axios, 'get');
    getMock.mockImplementation((url) => {
      switch (url) {
        case 'mainUrl-mySubId':
          return Promise.resolve({
            data: {
              "id": 1,
              "name": "home",
              "subCategories": [
                {
                  "id": 2,
                  "name": "sub1",
                  "url": "sub1Url",
                },
                {
                  "id": 3,
                  "name": "sub2",
                  "url": "sub2Url",
                }
              ]
            }
          });
        case 'sub1Url':
          return Promise.resolve({
            data: [
              {
                "id": 4,
                "title": "asset1"
              },
              {
                "id": 5,
                "title": "asset2"
              }
            ]
          });
        case 'sub2Url':
          return Promise.resolve({
            data: [
              {
                "id": 6,
                "title": "asset3"
              },
              {
                "id": 7,
                "title": "asset4"
              }
            ]
          });
      }
    });
    
    
    test('getData', async () => {
      const asyncDispatch = fetchNavigationFeed('mainUrl-__JTV__SUBSCRIBER__ID__', 'mySubId');
      const dispatch = jest.fn();
      await asyncDispatch(dispatch);
      expect(dispatch).toHaveBeenCalledTimes(2);
      const firstCallArgs = dispatch.mock.calls[0];
      expect(firstCallArgs).toEqual([{
        type: FETCH_NAVIGATION_FEED
      }]);
      const secondCallArgs = dispatch.mock.calls[1];
      expect(secondCallArgs).toEqual([{
        type: FETCH_NAVIGATION_FEED_SUCCESS,
        payload: [
          {
            id: 2,
            name: 'sub1',
            url: 'sub1Url',
            assets: [
              {
                "id": 4,
                "title": "asset1"
              },
              {
                "id": 5,
                "title": "asset2"
              }
            ]
          },
          {
            id: 3,
            name: 'sub2',
            url: 'sub2Url',
            assets: [
              {
                "id": 6,
                "title": "asset3"
              },
              {
                "id": 7,
                "title": "asset4"
              }
            ]
          }
        ]
      }]);
    });
    

    Note: you can use axios.all() but according to this thread it uses Promise.all() under the hood anyway.