0

I have two JSONs. One is from a variable and one from API call based on both of them I'm trying to generate HTML code. I'm using a recursive function as the JSONs can contain nested sections. Those JSON may also contain several 'selects' which should have 'options' based on another API call ("source"). When I'm trying to execute the code everything works, but instead of 'options' the function returns [object Promise] while in the console there are proper values.

Here is an example of the variable (API response is similar):

var constructor = {
    "element" : {
        "data" : [
            {
                "type" : "section",
                "id" : "id1",
                "elements" : [
                    {
                        "type" : "input",
                        "subtype" : "text",
                        "label" : "Address",
                        "id" : "address",
                    },
                    {
                        "type" : "section",
                        "id" : "id2",
                        "elements" : [
                            {
                                "type" : "h3",
                                "text" : "Some text"
                            },
                            {
                                "type" : "select",
                                "label" : "Klient",
                                "id" : "id3",
                                "width" : 100,
                                "elements" : "source",
                                "source" : "source_path"
                            }
                        ]
                    },
                    {
                        "type" : "section",
                        "id" : "id4",
                        "elements" : [
                            {
                                "type" : "h3",
                                "text" : "Some text 2"
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

And here is the code:

async function getData(url = '') {
  const response = await fetch(url, {
    method: 'GET',
    cache: 'no-cache',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json'
    }
  });
  return response.json();
}

const generateElements = (obj) => {
    if(obj.type == 'input') doSomething();
    if(obj.type == 'h3') doSomething();
    if(obj.type == 'select'){
        return `<select id="${obj.id}">
            ${
                typeof obj.elements == 'object' ?
                    Array.isArray(obj.elements) &&
                        obj.elements.map((element) => {
                            return `<option id="${element}">${element}</option>`
                        }).join("")
                : obj.elements == 'source' &&
                    getData(obj.source)
                        .then(data => {
                            data.map((element) => {
                                console.log(element.id, element.name)
                                return `<option id="${element.id}">
                                    ${element.name}
                                    </option>`
                            }).join('')
                        })
            }
            </select>
            <label for="${obj.id}">${obj.label}</label></div>`
    }
    if(obj.type == 'section'){
        return `<section id="${obj.id}">
        ${ obj.hasOwnProperty('elements') &&
            obj.elements.map((element) => {
                return generateElements(element)
            }).join('')
        }
        </section>`
    }
}

getData('the_url2')
    .then(data => {
    data.map((element) => {
        element.elementToAppend &&
            jQuery(element.elementToAppend).append(generateElements(JSON.parse(element.data)))
    })
});

constructor.map((element) => {
            jQuery(element.elementToAppend).append(generateElements(JSON.parse(element.data)))
})

I've tried to change the generateElements function to async but then the code generates [object Promise] in place of sections. My another idea was to await for API calls but then 'await is only valid in async function' error appeared. When I assign the API call from select to const and then return the value it generates proper html code in the console, but still [object Promise] in the result of the function. I've also tried many other solutions but without success.

I have no idea how to proceed with that but I'm not able to change API responses. I'm limited by Qualtrics with using frameworks and libraries.

EDIT: Thanks to @Bergi support I was able to resolve my problems. So:

I used async for generateElements:

const generateElements = async (obj) => { ... }

await for the fetch function:

await getData(obj.source)
    .then(data => {
        data.map((element) => {
            console.log(element.id, element.name)
            return `<option id="${element.id}">
                ${element.name}
                </option>`
        }).join('')
    })

and Promise for recursivness:

( await Promise.all(obj.elements.map(async (element) => {
        return await generateElements(element)
    })
) ).join('')

Thanks @Bergi once again! :)

  • Yes, `generateElements` should be a `async function`, as it does asynchronous things and has good reasons to use `await`. It will then return a promise, which you will have to wait for (with `await` or `then()`) before being able to append the promise results to the element. – Bergi May 12 '20 at 14:09
  • Thanks for the answer. How can I use recursiveness in this case? When generateElements is async and JSON contains a nested section calling the function second time generates error 'await is only valid in async function' – Sławek Orliński May 12 '20 at 14:23
  • You should rework your code so it operates on the assumption of async first from the top down, and/or put your dom manipulation stuff into a callback, since it is supposed to wait for generateElements to finish – user120242 May 12 '20 at 15:01
  • 1
    @SławekOrliński That's probably because you're doing the recursive call in a `map` callback. See [here](https://stackoverflow.com/a/37576787/1048572) for async loops – Bergi May 12 '20 at 15:20

1 Answers1

-1

If you are trying to get the API response data instead of a promise, you probably need to await response.json().

async function getData(url = '') {
  const response = await fetch(url, { /* ... */ });
  // return response.json();
  return await response.json();
}

And as @bergi points out your generateElements function needs to be async as well.

                typeof obj.elements == 'object' ?
                    Array.isArray(obj.elements) &&
                        obj.elements.map((element) => {
                            return `<option id="${element}">${element}</option>`
                        }).join("")
                : obj.elements == 'source' &&
                    (await getData(obj.source)) // ** await here
                      .map((element) => {
                         console.log(element.id, element.name)
                         return `<option id="${element.id}">
                               ${element.name}
                               </option>`
                      })
                      .join('')
joshwilsonvu
  • 2,569
  • 9
  • 20