How to handle recursive asynchronous DB calls that

2019-09-10 21:57发布

问题:

Building a JS app with Ionic framework and Firebase.

I want the app to suggest to each user a list of users to discuss with based on countries they visited. Come firsts in the suggested list the users with most common countries than the current user.

I have two tables in Firebase:

"users" : {
"userA" : {
  "countries" : [ {
    "id" : 4,
    "name" : "france"
  }, {
    "id" : 2,
    "name" : "spain"
  } ]
},
"userB" : {
  "countries" : [ {
    "id" : 4,
    "name" : "france"
  }, {
    "id" : 2,
    "name" : "russia"
  } ]
},
"userC" : {
  "countries" : [ {
    "id" : 4,
    "name" : "france"
  }, {
    "id" : 2,
    "name" : "spain"
  } ]
}

And an index table:

"country_user" : {
  "france" : { "userA" : true, "userB" : true, "userC" : true },
  "spain" : { "userA" : true, "userC" : true },
  "russia" : { "userB" : true }
}

On the front end I want $scope.matches to store a list of users ranked as explained above.

On the server side I implemented the following logic:

  1. Retrieve the list of the countries the current user has visited and return a promise with these countries
  2. For each country retrieve in table country_user the list of users corresponding to this country
  3. Create a "matches" array, parse each results of 2 and update the "matches" array

Promise in 1:

function getUserCountryListPromise(_uid){
        return firebase.database().ref('users/' + user.uid + '/countries/').once('value').then(function(snap){
          var userCountryList = [];
          if(snap.exists()){
            snap.forEach(function(country){
              userCountryList.push(country.val().name);
            });
          }
          return userCountryList;
        });
      }

And I started to implement 2 but I am stuck:

function getRegistrationsToCountries(_country){
        return firebase.database().ref('/country_user/' + _country).once('value').then(function(snap){
          if(snap.exists()){
            snap.forEach(function(registeredUser){


        });
      }

In case of userA I want to call getRegistrationsToCountries twice and only when those two calls are done (but number of calls is dynamic) I want to parse them to update a "matches" list that I want to return to the front-end.

Any suggestions?

Thanks

回答1:

Use the $q.all method on a hash of promises:

function getRegistrationsFromCountryList(countryList){
    var promiseHash = {};
    countryList.foreach(function (country) {
        promiseHash[country] = firebase.database().ref('/country_user/' + country).once('value');

    });
    return $q.all(promiseHash);
};

The above example returns a promise that resolves to a hash of registrations keyed to each country in the countryList.

To use with the countryListPromise, simply chain the promises:

var promise = getUserCountryListPromise(uid);

var registrationsHashPromise = promise.then(function(countryList) {
    //return promise to chain
    return getRegistrationsFromCountryList(countryList);
});

registrationsHashPromise.then(function(regsHash) {
    angular.forEach(regsHash, function(regs, country) {
       console.log(country + ": " + regs); 
       //update matches list
    });
});

Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.

--AngularJS $q Service API Reference -- Chaining Promises.