user7691120
user7691120

Reputation:

JavaScript promise bypassed resolve and proceed to .then()

I was having some problem with nested promise which resulting in forgotten promise problem.

let promiseList = new Promise((resolve, reject) => {
    //first query to retrieve from firebase
        query.once( 'value', data => {
            var promises = [];

            data.forEach(snapshot => {
                //get item key

                //second query based on item key                                    
                var promise = query.once('value');
                promises.push(promise);

                promise.then(data => { 
                    var itemDetail = data.val();

                    var receiptID = itemDetail.receiptID;
                    // third query to find matching receiptID
                    var query = firebase.database().ref('receipts');
                    query.once('value', data => {   
                        data.forEach(snapshot => {

                            snapshot.forEach(childSnapshot => {
                                if(childSnapshot.key == receiptID){
                                    var branchDetail = childSnapshot.val().branch;
                                    var branchName = branchDetail.branchName;

                                    //console.log('inside promise ' + branchName);
                                    datasetarr.push({branchName: branchName});
                                }
                            });

                        });
                    }); 

                });
            }); 

            // wait till all promises are finished then resolve the result array
            Promise.all(promises).then(() => resolve(datasetarr)); 
        });             
    });

// print out array here
promiseList.then((arr) => {
for(var i = 0; i < arr.length; i++){
    console.log(arr[i].branchName);
}   
});

I managed to print out the data from the console.log with 'inside promise'. However, when I tried to print it out from the .then(), there is nothing shown.

The problem now is it actually ran the .then() first before I resolve the promise.

Any ideas?

Upvotes: 0

Views: 349

Answers (2)

Leonardo Chaia
Leonardo Chaia

Reputation: 2835

I've never used Firebase, but I do know promises. Check this sample chaining promises, notice the return statements which produce the chaining.

var outerPromise = query.once('value').then(data => {
    // Promise array to group 2nd level promises and then do a Promise.all.
    var promises = [];
    // This will be the main output of the outerPromise.
    // We will populate it asynchronously inside our 2nd level promises.
    var datasetarr = [];
    data.forEach(snapshot => {
        // 2nd level promises, will be appended to the promises array.    
        // and will be enchained with the 3d level promise.
        var promise = query.once('value').then(data => { 
            var itemDetail = data.val();
            var receiptID = itemDetail.receiptID;
            var query = firebase.database().ref('receipts');
            // Third level promise. It's enchained by the return statement.
            return query.once('value').then(data => {   
                data.forEach(snapshot => {
                    snapshot.forEach(childSnapshot => {
                        if(childSnapshot.key == receiptID){
                            var branchDetail = childSnapshot.val().branch;
                            var branchName = branchDetail.branchName;

                            //console.log('inside promise ' + branchName);
                            datasetarr.push({branchName: branchName});
                        }
                    });
                });
            }); 
        });
        promises.push(promise);
    }); 

    // We wait until 2nd (and third) level promises are ready
    // and the return our desired output, the datasetarr
    return Promise.all(promises).then(()=> datasetarr);
});             

// Since it's all chained, the outerPromise will resolve once all promises are completed
// and we can get the output we supplied in the last chaining.
outerPromise.then((arr) => {
    console.log(arr)  
});

Upvotes: 1

Jared Smith
Jared Smith

Reputation: 21926

That's not how promises work, there's seldom if ever a need to nest them. If query.once already returns a promise that's great, but otherwise you'll need to wrap it:

let returnsPromise = value => new Promise(res => query.once(value, data => res(data));

Again, if it already returns a promise that's unnecessary, but I'm not a firebase guy. At any rate, now you can do something like this:

let result = returnsPromise('value')
  // run secondary query based on item key
  .then(data => Promise.all(data.map(item => returnsPromise(item.key)))
  // now do stuff with those results
  .then(data => {
    return Promise.all(data.map(item => {
      let receiptID = item.val().receiptID;
      // Note that the same 'wrap if not already returning promise
      // idea' is applicable here, but for illustration I'm just
      // going to act like this returns a promise.
      // Also note that while I've been rather down on nesting
      // its more or less necessary here because you need to capture
      // the receipt ID from the surrounding scope.
      return firebase.database().ref('receipts')
        .once('value')
        .then(snapshot => {
          return snapshot
            .filter(x => x.key === receiptID)
            .map(x => {
              let branch = x.val().branch.branchName;
              return {branch: branch};
            });
        });
  }))
  // Now we have an array of arrays of results but we want to
  // remove the nesting.
  .then(arrayOfArrays => arrayOfArrays.reduce((x,y) => { return x.concat(y); }, []));

Now you have a result promise that contains the array of values. You can call then on it and iterate over it:

result.then(arr => arr.forEach(x => console.log(x.branchName)));

Upvotes: 0

Related Questions