I'm also new to Q and promises, and have been struggling with this issue for days. I'm trying to iterate through a variable-length array of RECORDS, using an ID from each record in an async call to retrieve an OBJECT (in the case from redis).
The twist is that I need to combine some data from the RECORD with some data from the retrieved OBJECT, creating a new array from these combined objects which is then returned.
My failing code looks like this:
arrayOfThingRecords = [... an array of small objects, each with a 'thingid'...];
arrayOfCombinedObjects = [];
arrayOfThingRecords.forEach(function(thingRecord) {
Q.ninvoke(redisClient, "HGETALL", thingRecord.thingid)
.then((function (thingObject) {
combinedThingObject = {
thingStuffFromRecord: thingRecord.thingStuffFromRecord,
thingStuffFromObject: thingObject.thingStuffFromObject
};
}).done(function () {
arrayOfCombinedObjects.push(combinedThingObject)
}); //
}; // Then do stuff with arrayOfThingObjects...
I know that using the forEach
is wrong, because it executes before the promises return. I've been trying to work with Q.all()
and Q.settled()
, and with building an array of promises, etc, but I'm completely confused and suspect/hope that I may be making this harder than it needs to be.
Don't use a global arrayOfCombinedObjects = []
that you manually fill. Always resolve your promises with the result values of the respective operation. For example,
Q.ninvoke(redisClient, "HGETALL", thingRecord.thingid)
.then(function(thingObject) {
return {
// ^^^^^^
thingStuffFromRecord: thingRecord.thingStuffFromRecord,
thingStuffFromObject: thingObject.thingStuffFromObject
};
});
becomes a promise for that object.
Now, using Q.all
is the correct approach. It takes an array of promises, and combines them to a promise for an array of all the results. So we need to build an array of promises - an array of the promises for these objects from above. You can use the forEach
iteration and push
to put the array together, but using map
is much easier. It then becomes
var arrayOfThingRecords = [... an array of small objects, each with a 'thingid'...];
var arrayOfPromises = arrayOfThingRecords.map(function(thingRecord) {
return Q.ninvoke(redisClient, "HGETALL", thingRecord.thingid)
// ^^^^^^ let the promise be part of the new array
.then(function(thingObject) {
return {
thingStuffFromRecord: thingRecord.thingStuffFromRecord,
thingStuffFromObject: thingObject.thingStuffFromObject
};
});
});
Q.all(arrayOfPromises).then(function(arrayOfCombinedObjects) {
// Then do stuff with arrayOfThingObjects...
});