Derek
Derek

Reputation: 929

Don't include q promise result in sequentially chained promises

I have the following sequentially chained Q promise that fills in a hierarchy of user->properties->tenants->reminders:

    return getAllUsers()    // get all users of the system
    .then(function(users) {
        return Q.all(users.map(function(user) {
            return getUserProperties(user)  // foreach user of the system get their properties
            .then(function(properties) {
                user.properties = properties;
                return Q.all(properties.map(function(property) {
                    return getPropertyTenants(property) // foreach property get active tenants
                    .then(function(tenants) {
                        property.tenants = tenants;
                        return Q.all(tenants.map(function(tenant) {
                            return getTenantReminders(property, tenant) // foreach tenant get their payment reminders
                            .then(function(reminders) {
                                // if reminders.length == 0 we don't want to include this user
                                tenant.reminders = reminders;
                            });
                        }));
                    });
                }));
            }).thenResolve(user);
        }));
    });

The point of this promise chain is to get all payment reminders for a tenant and then send an email to each user with a list of the tenant reminders (per user). If there are no reminders resolved from getTenantReminders() then ideally the user from the top-level getAllUsers() would not be included in the result.

I thought about throwing a special error from getTenantReminders().then() handler that goes up the chain and is handled in thenResolve(user) which then does NOT return the user. Not sure if this is the proper way.

Appreciate any advice.

Upvotes: 2

Views: 93

Answers (1)

JLRishe
JLRishe

Reputation: 101652

As Seth says, the key to keeping your sanity here is to avoid the pyramid of doom. Here is an approach that I believe should work (though obviously I have not tested it):

// Produces a function that takes an array of values as input,
// Applies func() to each of them, and merges the results into a 
// promise for a single array.
// func() is assumed to take a single value as input and 
// return an array or promise for an array
// (X -> Promise<Y[]>) -> (X[] -> Promise<Y[]>)
function applyAllAndMerge(func) {
    return function (values) {
        return Q.all(values.map(func))
        .then(function (arrays) {
            return Array.prototype.concat.apply([], arrays);
        });
    };
}

// returns a promise for an array of all tenant reminders for the
// specified property
// property -> Promise<tenantReminder[]>
function getTenantRemindersForProperty(property) {
    return getPropertyTenants(property)
    .then(applyAllAndMerge(function (tenant) {
        return getTenantReminders(property, tenant);
    }));
}

// returns a promise for an array of all tenant reminders for the 
// specified user
// user -> Promise<tenantReminder[]>
function getTenantRemindersForUser(user) {
    return getUserProperties(user)
    .then(applyAllAndMerge(getTenantRemindersForProperty));
}

// returns a promise for an object containing the specified
// user and all of their tenant reminders
// user -> Promise<{user, tenantReminder}[]>
function getUserAndTenants(user) {
    return getTenantRemindersForUser(user)
    .then(function (reminders) {
        return { user: user, reminders: reminders };
    });
}        

return getAllUsers()    // get all users of the system
.then(function(users) {
    return Q.all(users.map(getUserAndTenants));
})
.then(function (userReminders) {
    // get rid of users with no reminders
    var usersWithReminders = userReminders.filter(function (ur) {
        return ur.reminders.length > 0;
    });

    // use usersWithReminders
});

Upvotes: 3

Related Questions