0

I have this code in which I add users to the Firebase DB, and resolve the Promise when the user import is done. In the resolve, I include arrays which contain the users that were added. These arrays are populated when a single user is added. The code is below:

return new Promise((resolve, reject) => {
    let usersAdded = [];
    users.map((user, index) => {
        db.ref('/users').push(user, err => {
            if(!err) {
                usersAdded.push(user)
                console.log(`Array at iteration ${index}: ${usersAdded}`)
            }
            if(index == users.length-1){
              resolve({status: true, usersAdded})
            }
        })
    })
})

Console Output:

Array at iteration 0: [{name: nadir, email: [email protected], creditCard: true}]
Array at iteration 1: [{name: nadir, email: [email protected], creditCard: true}, {name: arslan, email: [email protected], creditCard: true}]
Array at iteration 2: [{name: nadir, email: [email protected], creditCard: true}, {name: arslan, email: [email protected], creditCard: true}, {name: farhan, email: [email protected], creditCard: true}]

Although when I see the response on .then(user), it returns an empty array (usersAdded). Users get added in Firebase as well. Can anyone tell me what is the problem here?

Edit:

I was able to solve it, the problem was that the Promise was resolved before the db.ref.push triggered the callback, in that callback, I was populating the array, as it was an async operation.

9
  • 3
    Shouldn't you be calling Promise.all on an array of Promises if you want to iterate over all users? Commented Dec 7, 2018 at 6:23
  • 1
    You can only resolve a promise once. Your code looks like it's trying to resolve a promise as many times as there are elements in users. Not sure what you're intending here. Commented Dec 7, 2018 at 6:25
  • Hey, @DougStevenson glad you answered a big fan of you, as you can see I have an if statement in which the condition is only true when the loop is on the last iteration.
    – Abbas
    Commented Dec 7, 2018 at 6:28
  • @CertainPerformance This Promise is inside a function, which function is called by another function. So when it is resolved on the last iteration of the loop, it should return the usersAdded array, which ten will be send to the user using res.send()
    – Abbas
    Commented Dec 7, 2018 at 6:30
  • What you're doing will only work if the asynchronous db.ref...push executes serially - otherwise, there's no guarantee that the last item push is called with will be the last asynchronous operation to complete. Better to use Promise.all, though I'm not sure why the usersAdded result would be empty, I'd expect it to contain at least one item (unless there was an error, in which case you might want to reject - also avoid the explicit Promise construction antipattern) Commented Dec 7, 2018 at 6:33

1 Answer 1

1

Like CertainPerformance said in the comments your code only works assuming the push callbacks get called in order. Since they are most likely asynchronous the order of the callback execution is not guaranteed. This means if the last push callback is called first you promise already resolves. For this reason you should use Promise.all() to wait until all the push callbacks are resolved then handle the results.

return Promise.all(users.map(user => {
    return new Promise(resolve => db.ref('/users').push(user, resolve));
}))
.then(pushResults => { // all push callbacks are resolved
    return pushResults
        .map((error, index) => error ? null : users[index])
        .filter(Boolean);
});

You might however want to call reject if none of the users successfully saved. This makes the code more complicated, but you can than use catch on the returned promise if no users where saved.

// return a resolved promise if the users array is empty
if (!users.length) return Promise.resolve(users);

// handle a non-empty users array
return Promise.all(users.map(user => {
    return new Promise(resolve => db.ref('/users').push(user, resolve));
}))
.then(pushResults => {
    // all users had errors
    if (pushResults.every(Boolean)) 
        return Promise.reject(pushResults);

    // one or multiple users had no errors
    return pushResults
        .map((error, index) => error ? null : users[index])
        .filter(Boolean);
});

Returning a value other than a promise inside a then function will simply be forwarded to the next then as if you returned Promise.resolve(value).

Not the answer you're looking for? Browse other questions tagged or ask your own question.