【问题标题】:Getting promise from a nested forEach loop?从嵌套的 forEach 循环中获得承诺?
【发布时间】:2017-06-22 14:10:49
【问题描述】:

我正在尝试遍历 Firebase 列表(使用 AngularFire2),并且我想获得一个 Promise 来指示嵌套循环何时完成,以便我可以处理第二个操作。我正在使用以下代码:

  console.log('START');
  this.af.database.list('Sites/123123/Users')
    .map(users => {
      users.map(user => {
        console.log('found a user ', user);
        this.af.database.list('registeredUsers/' + user.$key + '/Sites')
          .forEach(sites => {
            sites.forEach(site => {

              if (site.$value == '123123') {
                console.log('found site ', site.$key);
              }
            });
          });
      });
    }).first().toPromise().then(function (x) {
      console.log('ALL DONE')
    });

输出是:

但是我只在外循环之后才得到 Promise,而不是等待嵌套循环完成。有什么建议吗?

【问题讨论】:

  • 如果您使用.map() 循环创建多个承诺,那么您将需要使用Promise.all() 来等待它们。 Promise.all() 创建一个等待一系列承诺的承诺(您可以返回)。
  • 把 Promise.all() 放在哪里!?
  • 我不知道你的数据库接口,但大概this.af.database.list() 是异步的,你需要从中得到一个承诺并从你的.map() 返回它。这意味着.map() 将产生一个promise 数组,您需要在promise 数组上使用Promise.all(),这样您就可以返回Promise.all() 创建的单个主promise。
  • 你能举个例子吗?我在使用 promises 数组之前尝试过,但也没有用
  • 对不起,但我不明白您的数据库如何产生承诺给您一个具体的例子。您可以在 MDN 和网络上的许多其他地方阅读有关 Promise.all() 的信息。这个概念是你使用类似.map()的东西来产生一个promise数组,然后你在那个promise数组上调用Promise.all(),等待它们全部完成并将所有结果收集到一个数组中。

标签: typescript firebase promise firebase-realtime-database angularfire2


【解决方案1】:

您在 map 调用中缺少 return 语句,因为您在用户 observable 的第一个映射内生成新的 Observables,您应该在此处使用 mergeMap:

console.log('START');
this.af.database
    .list('Sites/123123/Users')
    .mergeMap(users => {
        return users.map(user => {
            console.log('found a user ', user);
            return this.af.database.list('registeredUsers/' + user.$key + '/Sites')
                .forEach(sites => {
                    sites.forEach(site => {
                        if (site.$value == '123123') {
                            console.log('found site ', site.$key);
                        }
                    });
                });
        });
    })
    .first().toPromise().then(x => {
        console.log('ALL DONE')
    });

这里我有一个简化版本的代码,消除了对你的 firebase 应用程序的依赖:

//const Rx =  require('rxjs'); // require Rx on nodejs instead of html script tag
const usersObservable = Rx.Observable.from([[{ id: 1, $key: 123 }]]);

const sitesObservable = Rx.Observable.from(
    new Promise((resolve) => {
        setTimeout(() => {
            resolve([{ $value: '123123', $key: 'site1' }, { $value: '0', $key: 'site2' }])
        }, 500);
    })
);

console.log('START');
usersObservable
    .mergeMap((users) => {
        return users.map(user => {
            console.log('found a user ', user);
            return sitesObservable
                .forEach(sites => {
                    sites.forEach(site => {
                        if (site.$value == '123123') {
                            console.log('found site ', site.$key);
                        }
                    });
                });
        });
    })
    .first().toPromise().then(x => {
        console.log('ALL DONE')
    });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.js"></script>

这是生成的输出:

START
found a user  {
  "id": 1,
  "$key": 123
}
found site  site1
ALL DONE

添加您的特定 firebase 代码后,您的代码应如下所示:

console.log('START');
var siteUsers = this.af.database.list('Sites/-KcF5J9SoSHDrUEYO-Ed/Users');

siteUsers
    .mergeMap(users => {
        console.log('users = ', users);
        return users.map(user => {
            console.log('found a user ', user);
            return this.af.database.list('registeredUsers/' + user.$key + '/Sites')
                .forEach(sites => {
                    console.log('sites = ', sites)
                    sites.forEach(site => {
                        if (site.$value == '-KcF5J9SoSHDrUEYO-Ed') {
                            console.log('found site ', site.$key);
                        }
                    });
                });
        });
    })
    .first().toPromise().then(x => {
        console.log('ALL DONE')
    });

【讨论】:

  • 我也试过了,同样的,它只在外循环之后不断给我承诺,而不是等待嵌套完成
  • 如果您无法通过 map 函数中的 return 语句获得正确的行为,请确保所有 *.database.list(...) 调用都返回一个可观察的且没有简单数组
  • database.list(..) 返回 FirebaseListObservable
  • 我发现了问题,您需要在用户 listObservable 上使用 mergeMap 而不是 map 运算符
  • 我用mergeMap尝试了上面的代码,我没有收到ALL DONE消息!!?
【解决方案2】:

@卡勒

我也试过这个方法,也一样:

 console.log('START');
  var siteUsers = this.af.database.list('Sites/-KcF5J9SoSHDrUEYO-Ed/Users');
  const siteUsersObservable = Observable.from(siteUsers);

  siteUsersObservable
    .map(users => {
      console.log('users = ', users);
      return users.map(user => {
        console.log('found a user ', user);
        var userSites = this.af.database.list('registeredUsers/' + user.$key + '/Sites');
        const userSitesObservable = Observable.from(userSites);
        return userSitesObservable
          .forEach(sites => {
            console.log('sites = ', sites)
            sites.forEach(site => {
              if (site.$value == '-KcF5J9SoSHDrUEYO-Ed') {
                console.log('found site ', site.$key);
              }
            });
          });
      });
    })
    .first().toPromise().then(x => {
      console.log('ALL DONE')
    });

firebase 结构是这样的:

我的结果是:

【讨论】:

    猜你喜欢
    • 2019-01-23
    • 1970-01-01
    • 1970-01-01
    • 2021-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-24
    • 1970-01-01
    相关资源
    最近更新 更多