JavaScript递归Promise

8
我正在尝试使用Promises创建递归函数,但好像无法得到正确的结果。 我已经有了不使用promises的工作代码,但它使用计数器和全局变量等并不完美,因此我正在重写该代码,并创建可以重复使用的模块。
本质上,该函数应该从Active Directory获取用户,并递归查找其所有直接报告者、直接报告者的直接报告者等等。
我已经尝试了很多版本的这个函数,以下是当前版本:
function loadReports(personEmail, list) {
    return new Promise((resolve, reject) => {
        getAccessTokenPromise()
            .then(access_token => {
                list.push(personEmail);
                return makeRequest(personEmail, access_token);
            }).then(result => {
                if (result.value.length > 0) {
                    Promise.all(result.value.map(person => {
                        loadReports(person.userPrincipalName, list);
                    })).then(resolve());
                } else {
                    resolve();
                }
            })
            .catch(e => reject(e));
    });
}
getAccessTokenPromise 函数执行一个访问令牌请求并返回一个 Promise 对象。 makeRequest 函数再次为用户和其报告进行 https 请求,并返回一个包含结果的 json 对象作为 Promise。
非常感谢您的意见。D.

1
“但它使用计数器和全局变量等” - 现在你看到了无纯函数和自由变量是多么邪恶。首先重新实现它,使其不依赖于外部作用域的变量,然后将其转换为Promise。 - zerkms
1个回答

7
为了让递归与Promise配合使用,通常要将所有递归的Promise链接到它们的调用者。为了做到这点,你必须从你的`.then()`处理程序中返回任何Promise,以便将它们链接到原始的Promise。这也倾向于消除你在Promise反模式中包装现有Promise的手动创建Promise的问题。以下是一种实现方法:
function loadReports(personEmail, list) {
    return getAccessTokenPromise().then(access_token => {
        list.push(personEmail);
        return makeRequest(personEmail, access_token);
    }).then(result => {
        if (result.value.length > 0) {
            return Promise.all(result.value.map(person => {
                return loadReports(person.userPrincipalName, list);
            }));
        }
    });
}

修改内容:

  1. 在调用getAccessTokenPromise()之前添加return,这样我们就可以返回初始承诺。这也使我们消除了反模式中涉及到的new Promise()以及所有手动拒绝和解决的需要。

  2. 在递归的loadReports()之前添加return。必须这样做才能允许.map()在将承诺传递给Promise.all()之前收集该承诺。

  3. Promise.all()之前添加return,以便将其链接到原始承诺链。


您必须确保数据库数据中永远不会出现任何循环情况(数据库上的错误导致报告形成循环列表)。A报告给B,B报告给C,C报告给A。因为如果出现这种情况,这段代码将一直执行下去,永远无法完成(可能最终耗尽某些系统资源)。

如果这是我的代码,我可能会创建一个人员访问过的Set,并拒绝在任何已访问过的人员上调用loadReports()。这样就可以避免循环情况。如果看到这种情况,您可能还想log()一下,因为它可能是数据库错误/破坏。


谢谢。是的,这样更好,也更整洁。非常感谢您的解释,非常有用。我通过将最后的.then(resolve());更改为.then(results => { resolve()});,使用我的原始代码获得了一个可行的解决方案,但我更喜欢您的解决方案。再次感谢。 - Darren
另外,也感谢你提供的最后一部分内容,在尝试之前我还没有通过“代码/更改”部分。这很有趣,因为我最终想要一个Set,我正在将结果与另一个系统上的成员身份进行比较,所以我可能会引入它。这些信息来自http://graph.microsoft.io,希望是可靠的;)。 - Darren
1
@Darren,你应该考虑只使用.then(resolve)。 :) - Kyle Baker
太棒了,而且简明扼要!如果能看到使用async/await实现的相同示例,那将非常有用,因为它们现在是标准(Chrome和Firefox在稳定版本中支持它们)。 - Ciprian Tomoiagă

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接