异步递归函数

时间:2018-01-02 23:12:53

标签: javascript asynchronous async-await

我正在尝试创建一个节点js alexa app来控制我的本地kodi实例。我使用Node-Kodi作为JsonRPC API的包装器。在整个应用程序中,我一直在使用Async / await而没有任何问题。我的代码可以在我的公共回购中看到here

当我尝试执行递归异步功能时,我遇到了问题。

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;
}

似乎正在发生的事情是我只在第一级后得到一个返回值,而且函数没有等待构建子菜单。

使用youtube插件作为示例,控制台输出如下所示:

[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' }

我不是JavaScript而是PHP开发人员,所以我很可能没有正确使用异步函数。请有人指出我正确的方向。

可在此Branch

上找到完整代码

1 个答案:

答案 0 :(得分:1)

buildAddonMenu必须是async函数,并使用await运算符进行调用。虽然菜单可能会继续异步构建(因为buildAddonMenu返回一个可以在以后更新的对象),尝试在同步调用buildAddonMenu后立即呈现或检查菜单将失败 - 您只能获得由同步调用中提供的数据生成的第一级菜单项。

因此,对buildAddonMenu的每次调用都应采用

形式
 menuObject = await buildAddonMenu(....)

包括app.Intent来电中的内容。

对上述内容进行更改后,控制语句

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

显然想要为目录中的每个文件执行异步函数。提供的匿名函数确实需要async才能在函数体中使用await - 但forEach不会等待它对异步函数完成的调用。

Using async/await with a forEach loop有多种解决方案可以等待异步调用在循环中完成。如果顺序添加已完成的子菜单是可以接受的,那么最简单(和接受)的解决方案可能是创建一个实际的for循环,该循环在处理{的循环体内等待异步函数调用(使用await) {1}}条目。

一般来说,menu.files循环返回可枚举的继承属性,不建议通过对象的本地属性进行迭代。请改用for( ... in ...)甚至标准for( ... of ...)循环。