0

I am attempting to create a node js alexa app to control my local kodi instance. I am using Node-Kodi as a wrapper for the JsonRPC API. Throughout the app I have been using Async/await with no issues. My code can be seen here on my public repo.

I have come into issues when i am trying to do a recursive Async function.

app.intent("AddonInfoTest",
    {
        "slots": {"ADDON": "ADDON_SLOT"},
        "utterances": ["get info for +ADDON+"]
    },
    async function(request, response) {
        let addon = await kodi.addons.getAddonDetails({
            'addonid': 'plugin.video.youtube', 
            'properties': ['path']
        });

        console.log(addon.addon.path);

        let addonMenu = await kodi.files.getDirectory('plugin://plugin.video.youtube');

        let menu = buildAddonMenu(addonMenu);

        console.log('menu: ' + JSON.stringify(menu));  
    }
);

function buildAddonMenu(menu, depth = 0)
{
    var addonMenu = {};
    var subMenu = {};

    menu.files.forEach(async function(file) {
        addonMenu[file.label] = {
            'link': file.file
        }

        console.log(file);
        console.log(depth)

        if (file.filetype == 'directory' && !file.label.match(/^next page.*/i) && depth <= constants.addon_menu_max_depth) {
            try {
                let subMenu = await kodi.files.getDirectory(file.file);
                if (typeof subMenu !== 'undefined' && 'files' in subMenu) {
                    console.log('Building Sub Menu for ' + file.label);
                    // console.log('SubMenu: ' + JSON.stringify(subMenu));
                    addonMenu[file.label]['sub_menu'] = buildAddonMenu(subMenu, depth +1);
                }
            } catch (error) {
                console.error(error);
            }
        }
    });

    return addonMenu;
}

what seems to be happening is that i am getting a return value after only the first level and the function is not waiting for the submenu to be built.

Using the youtube plugin as an example the console output looks like the following:

[2018-01-02T22:49:58.435Z] { file: 'plugin://plugin.video.youtube/sign/in/',
  filetype: 'directory',
  label: '[B]Sign In[/B]',
  type: 'unknown' }
[2018-01-02T22:49:58.436Z] 0
[2018-01-02T22:49:58.437Z] { file: 'plugin://plugin.video.youtube/special/popular_right_now/',
  filetype: 'directory',
  label: 'Popular right now',
  type: 'unknown' }
[2018-01-02T22:49:58.437Z] 0
[2018-01-02T22:49:58.438Z] { file: 'plugin://plugin.video.youtube/kodion/search/list/',
  filetype: 'directory',
  label: 'Search',
  type: 'unknown' }
[2018-01-02T22:49:58.438Z] 0
[2018-01-02T22:49:58.439Z] { file: 'plugin://plugin.video.youtube/special/live/',
  filetype: 'directory',
  label: 'Live',
  type: 'unknown' }
[2018-01-02T22:49:58.439Z] 0
[2018-01-02T22:49:58.442Z] { file: 'plugin://plugin.video.youtube/config/youtube/',
  filetype: 'directory',
  label: 'Settings',
  type: 'unknown' }
[2018-01-02T22:49:58.442Z] 0
[2018-01-02T22:49:58.442Z] menu: {"[B]Sign In[/B]":{"link":"plugin://plugin.video.youtube/sign/in/"},"Popular right now":{"link":"plugin://plugin.video.youtube/special/popular_right_now/"},"Search":{"link":"plugin://plugin.video.youtube/kodion/search/list/"},"Live":{"link":"plugin://plugin.video.youtube/special/live/"},"Settings":{"link":"plugin://plugin.video.youtube/config/youtube/"}}
[2018-01-02T22:49:59.171Z] Building Sub Menu for Search
[2018-01-02T22:49:59.171Z] { file: 'plugin://plugin.video.youtube/kodion/search/input/',
  filetype: 'directory',
  label: '[B]New Search[/B]',
  type: 'unknown' }
[2018-01-02T22:49:59.171Z] 1
[2018-01-02T22:49:59.171Z] { file: 'plugin://plugin.video.youtube/kodion/search/query/?q=minecraft',
  filetype: 'directory',
  label: 'minecraft',
  type: 'unknown' }
[2018-01-02T22:49:59.171Z] 1
[2018-01-02T22:49:59.172Z] { file: 'plugin://plugin.video.youtube/kodion/search/query/?q=Hello+World',
  filetype: 'directory',
  label: 'Hello World',
  type: 'unknown' }
[2018-01-02T22:49:59.172Z] 1
[2018-01-02T22:49:59.172Z] { file: 'plugin://plugin.video.youtube/kodion/search/query/?q=hello+world',
  filetype: 'directory',
  label: 'hello world',
  type: 'unknown' }

I'm not a JavaScript rather a PHP Developer so more than likely I'm not using the asynchronous function correctly. Please someone point me in the right direction.

Full Code can be found on this Branch

Olian04
  • 6,480
  • 2
  • 27
  • 54
Nate
  • 35
  • 1
  • 9
  • 1
    You can't pass `async` functions to `forEach`. It isn't set up to handle `async` and won't wait for them. You can to use `for...in` instead – Charlie Martin Jan 02 '18 at 23:33
  • 1
    Possible duplicate of [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – Dan O Jan 02 '18 at 23:43

1 Answers1

1

buildAddonMenu needs to be an async function and called using the await operator. Although the menu may continue building asynchronously anyway (because buildAddonMenu returns an object that could be updated later), trying to render or inspect the menu immediately after a synchronous call to buildAddonMenu will fail - you only get the first level menu items made from data supplied in the synchronous call.

So every call to buildAddonMenu should take the form

 menuObject = await buildAddonMenu(....)

including that in the app.Intent call.

After making changes for the above, the control statement

 menu.files.forEach(async function(file) {...}

apparently wants to execute an asynchronous function for each file in a directory. The anonymous function supplied really does need to be async to use await in the function body - but forEach does not wait for calls it makes to an async function to complete.

Using async/await with a forEach loop has multiple solutions to waiting for async calls to complete in a loop. If adding completed sub-menus sequentially is acceptable, the simplest (and accepted) solution may be to create an actual for loop that waits on async function calls (using await) within a loop body that processes menu.files entries.

In general note that for( ... in ...) loops return enumerable inherited properties and are not recommended for iteration through local own-properties of an object. Use for( ... of ...) or even a standard for(...;...;...) loop instead.

traktor
  • 17,588
  • 4
  • 32
  • 53