I am working with SharePoint making JSOM calls. I need to get results and not move on until ALL data is retrieved. I have tried so many examples (none are complete enough for me to understand how to tailor to my issue with nested $.each loop statements. I seem to get close, but never anything working correctly.
I have edited my actual code (minus the top 3 variables as they are passed in from another page) to incorporate Tomalak's work so it we can figure it out better. Currently, the results are coming up with empty objects. Trying to figure out what I;m doing wrong.
[EDITED 06 AUG 2018]
Finally got it to work. there was only two tiny problems I found with the provided code :-). I will try and bold them.
var fya = [2008,2009]; //Fiscal Year Array which we use to know what lists to look at
var assignedRecords = []; //Global Reusable Variable
var assignedCourses = ['Math','Science','Reading']; //There might not be records who are associated with a particular course in each list. Wee need to pull the student records (assignedRecords) assoiciated with the above in 2008 and 2009.
SP.ClientContext.prototype.executeQueryPromise = function (items) {
var result = $.Deferred();
this.load(items);
this.executeQueryAsync(function (sender, args) { result.resolve(items) },
function (sender, args) { result.reject(args) });
return result.promise();
};
Move 'var arr = [];' outside of arrayFromCollection
Add 'var' to the e variable in that same function
var arr = [];
function arrayFromCollection(coll) {
var e = coll.getEnumerator();
while (e.moveNext()) arr.push(e.get_current());
return arr;
};
function queryListPromise(title, course) {
var list = hostWeb.get_lists().getByTitle(title);
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml('<View><Query><Where>'
+ '<Eq><FieldRef Name="Course"/><Value Type="Text">' + course + '</Value></Eq>'
+ '</Where></Query></View>');
return context.executeQueryPromise(list.getItems(camlQuery)).then(arrayFromCollection);
};
function GetAssignedApplications() {
assignedRecords = []; //Need to start empty
var myCourses = assignedCourses;
$.each(myCourses, function (i, myCoursesItem) {
var courseName = myCoursesItem.get_lookupValue();
Change '$.forEach' to '$.each'
$.each(fya, function (n, fyaItem) {
var listTitle = "FY" + String(fyaItem).substring(2); //FY18 & FY19 are the names of the actual lists.
assignedRecords.push(queryListPromise(listTitle, courseName));
});
});
$.when(assignedRecords).then(function (results) {
return Array.prototype.concat.apply([], results);
}).then(function (items) {
items.forEach(function (item) {
var a = item; //item is empty and this actually runs before arrayFromCollection and it returns duplicate records (4) when there is only 2.
});
}).fail(onError);
};
So first things first, waiting on multiple promises in jQuery is done with $.when().
$.when(p1, p2, p3).then(function (results) {
// results is an array of length 3
});
In order to make this work with a variable argument count, we use Function#apply:
var promises = [p1, p2, p3]; // could be be of any length now
$.when.apply($, promises).then(function () {
var results = [].slice.call(arguments);
// results is an array of the same length as promises
});
In order to make this reusable, let's call that $.whenAll:
$.whenAll = function (promises) {
return $.when.apply($, promises).then(function () {
return [].slice.call(arguments);
});
};
Now we can do:
$.whenAll([p1, p2, p3]).then(function (results) {
// results is an array of length 3
});
Next, we need to turn the callback-based executeQueryAsync() into a promise-based function. In general, promisification follows this scheme:
function somethingPromise(args) {
var result = $.Deferred();
somethingAsync(/* onSuccess */ result.resolve, /* onError */ result.reject);
return result.promise();
}
This expects that the onSuccess and onError handlers receive data as arguments. That's not the case with executeQueryAsync - it modifies the items directly. However, we still want to resolve the promise with items, so we need to do that explicitly.
Let's slap that onto the SP.ClientContext prototype right-away for re-use.
SP.ClientContext.prototype.executeQueryPromise = function (items) {
var result = $.Deferred();
this.load(items);
this.executeQueryAsync(
function (sender, args) { result.resolve(items); },
function (sender, args) { result.reject(args); }
);
return result.promise();
};
Next we should set up a helper that turns those unwieldy collections into something usable.
function arrayFromCollection(coll) {
var arr = [], e = coll.getEnumerator();
while (e.moveNext()) arr.push(e.get_current());
return arr;
}
With that all done, we can extract a function that takes a title and returns a promise for an item array.
function queryListPromise(title) {
var list = web.get_lists().getByTitle(title);
var q = new SP.CamlQuery();
//...
return ctx.executeQueryPromise(list.getItems(q)).then(arrayFromCollection);
}
And finally, since you want a flat list of items, we should start with a flat list of queries:
function GetNextFunction() {
var queries = [];
// build flat list of queries
$.each(arr1, function(i, arr1Item) {
$.each(arr2, function(n, arr2Item) {
queries.push(queryListPromise(arr2Item));
});
});
// wait on all queries
$.whenAll(queries).then(function (results) {
// flatten incoming array of arrays
return Array.prototype.concat.apply([], results);
}).then(function (items) {
// items contains all items from all queries
items.forEach(function (item) { /* ... */ });
}).fail(onError);
}
Here queries becomes a flat array of promises, each promise will resolve to an array of items. Therefore, results will be an array of arrays of items.
Related
In the code below, I am trying to get a value from a 'nodehun' method called spellSuggestions. In the documentation I'm told the syntax to use this method is as follows: dict.spellSuggestions(w, handleMisspellings);
where w is a list of words and handleMisspellings is a function (which is posted below). I can see the output on the console for handleMisspellings, but for the life of me, I cannot return or find a way to return a variable from this call: [dict.spellSuggestions(w, handleMisspellings);]. After setting a var equal to 'dict.spellSuggestions(w, handleMisspellings);' the return value is undefined. Please help!
var debugFlag = process.argv.indexOf('debug') > -1;
var nodehun = require('./../build/' + (debugFlag ? 'Debug' : 'Release') + '/nodehun');
var fs = require('fs');
var dict = new nodehun(fs.readFileSync(__dirname+'/dictionaries/en_US.aff'),fs.readFileSync(__dirname+'/dictionaries/en_US.dic'));
//var words = ['original', 'roach', 'erasee', 'come', 'consol', 'argumnt', 'gage',
// 'libary', 'lisence', 'principal', 'realy', 'license', 'suprise', 'writting'];
var handleMisspellings = function(err, correct, suggestions, origWord, callback) {
if (err) throw err;
if (correct) {
console.log(origWord + ' is spelled correctly!');
}
else {
console.log(origWord + ' not recognized. Suggestions: ' + suggestions);
}
var value = {
err: err,
correct: correct,
suggestions: suggestions,
origWord: origWord
};
console.log('VALUE+++++: ' + value);
return value;
}
var foo = function(words) {
words.forEach(function(w) {
dict.spellSuggestions(w, handleMisspellings);
some = dict;
console.log(JSON.stringify(some, null, 2));
});
}
module.exports = {
foo: foo
}
Thanks Dave. I eventually discovered the practical use of callback functions. For each method that contained data that I desired to access outside of the method, I declared an individual function to wrap the method. The function accepted two input arguments. The first was the input variable to drive the method call. The second was literally 'callback'. Inside the method, I would perform whatever operation I wanted to package the data into a JSON object before returning any desired data with 'return callback(var)'. In the call of the created wrapper function, I would actually call the function using the input variable of choice to drive the method in the function definition, and pass 'function(return_variable)' as the second argument. This creates a new method in which the desired data may be accessed or even again called back. The final code I desired performs a for loop on each element of a list of words, creates metadata about those words, and appends the unique data for each word to each word in a single array. The final array is a single object which contains all input words, and all data about those words. It required 4 individual functions (one of which was recursive), and a function call. Please see the code snippet of the function described above [doCall]. Note the use of the code begins at the call of 'analyze' [which is commented out here] and works its way up to each previous function declaration. I hope this helps someone else in the future to understand the functional use of 'callbacks'. Please ask if you have any questions, and Thanks again =D.
function doCall(word, callback) {
dict.spellSuggestions(word, function(err, correct, suggestions, origWord) {
if (err) throw err;
// if (correct)
// console.log(origWord + ' is spelled correctly!');
// else
// console.log(origWord + ' not recognized. Suggestions: ' + suggestions);
val = {
err: err,
correct: correct,
origWord: origWord,
suggestions: suggestions
}
return callback(val);
});
}
function addMember(array, index, callback){
doCall(array[index], function(val){
// console.log(val);
// console.log(index);
// console.log(array[index]);
// console.log(val.origWord);
array[val.origWord] = val;
// console.log(array[val.origWord]);
index = index + 1;
return callback(array, index);
});
}
function Loop(array, index, callback) {
addMember(array, index, function(array2, index2){
// console.log(index);
// console.log(index2);
if(index2 === array2.length) {
return callback(array2);
}
else{
Loop(array2, index2, callback);
}
});
}
function analyze(array, index, callback){
Loop(array, index, function(complete_array){
console.log('!!!!!!!!!!!!!!!!!' + complete_array);
return callback(complete_array);
});
}
/*
analyze(words, 0, function(complete_array){
// for(i = 0; i < complete_array.length; i++) {
console.log(complete_array);
// }
});
*/
module.exports = {
analyze
}
I have a Node/Express partial that is being called with AJAX, and is supposed to send a status update back to the view, after 2 subsequent API calls are made. This workflow relies on the csv-to-array module to read a ship-orders.csv file, and determine if the second API call (POST to Shipments) has already occured. It is supposed to do this by matching the OrderNumber in the csv file to the returned OrderNumber from the FindOrders endpoint (the first API).
The problem is that I am creating 2 arrays of order numbers to compare, but matching the first set of order numbers to the second set either always returns true or always returns false, and it very clearly should show "true" for the first record in the csv, and "false" for every other.
Before getting into the bulk of the code, here's the promise that reads the csv file into an array:
csv-to-array:
var csvShipPromise = new Promise(function(resolve, reject){
var csvColumns = ['ChannelName', 'OrderNumber', 'LineNumber', 'WarehouseCode', 'Qty', 'Carrier', 'TrackingNumber', 'Shipdate', 'ShipMethod'];
var csvShipArr;
var csvArr;
csvArray({
file: shipLog,
columns: csvColumns
}, function(err, array){
csvShipArr = array;
resolve(csvShipArr);
});
});
Next I have a long promise that gets executed when the request to the partial is made. The comparison between logged OrderNumbers and OrderNumbers that need to be posted to Shipments is the 5th "then" block (and it's commented in the code below).
router.get and chained promise:
router.get('/', function(req, res, next) {
findPromise.then(function(findData){
//Properly format xml string
var foundData = replaceAll(findData, '<', '<');
foundData = replaceAll(foundData, '>', '>');
return foundData;
}).then(function(foundData){
//Parse xml to JSON and stringify
var parsedFound;
parseString(foundData, function(err, result){ //uses an xml to json module
parsedFound = JSON.stringify(result);
});
return(parsedFound);
}).then(function(parsedStr){
//Parse JSON and return an array of objects
var parsedJson = JSON.parse(parsedStr);
var orders = parsedJson['soap:Envelope']['soap:Body'][0]['FindOrders'][0]['orders'][0]['order'];
return orders;
}).then(function(orders){
//Get only orders with a tracking number.
var trackArray = [];
var ord;
for(ord in orders){
var postObj = orders[ord];
if(postObj.TrackingNumber[0].length > 1){
trackArray.push(postObj);
}
}
return trackArray; //array of orders that contain tracking numbers
}).then(function(trackArray){
/**** This is the block that is causing problems. *****/
var tItm;
var loggedOrders = [];
for(tItm in trackArray){
var alreadyLogged = false;
var trackedItm = trackArray[tItm];
var trackedOrderNum = trackedItm.ReferenceNum;
csvShipPromise.then(function(csvOrders){
var csv;
var loggedOrderArr = [];
for (csv in csvOrders){
var csvItm = csvOrders[csv];
var csvOrderNum = csvItm.OrderNumber; //gets the OrderNumber as expected
loggedOrderArr.push(csvOrderNum);
}
return loggedOrderArr; //Return a simple array of all OrderNumbers
}).then(function(loggedOrderArr){
console.log(loggedOrderArr);
console.log(trackedOrderNum);
var ord;
for (ord in loggedOrderArr){
if(trackedOrderNum == loggedOrderArr[ord]){
console.log('found');
alreadyLogged = true;
}
else {
console.log('not found');
alreadyLogged = false;
}
}
return loggedOrderArr; //Simply returning this value because the alreadyLogged test isn't working.
});
/* Here is where the test fails.
It shouldn't, because there are, say, 4 OrderNumbers in the result of the first API call,
and only 1 Order number logged in the CSV.
So it should be true once, and false 3 times.
But it is true all the time.
*/
if(alreadyLogged){
console.log('found'); //Always logs true/found.
} else {
console.log('not found');
}
}
return trackArray; //Just passing the array to the view, for now.
}).then(function(obj){
res.send(obj);
return(obj);
}).catch(function(err){
console.log(err);
});
});
When I console.log the values of trackArray and loggedOrderArr, I see that there should be an intersection between an array of 4 values and an array of 1 value, but for some reason the comparison, if(trackedOrderNumber == loggedOrderArr[ord]) isn't working.
Alright, I'm gonna be honest, your code made my eyes swim. but as far as I can tell, a few things pop up:
move var alreadyLogged = false; to before the loop;
then add alreadyLogged = false; after if(alreadyLogged) statement
I think it has to do with scope. You are basically checking bool value of a var that has not changed yet because your promises has not resolved at the point of if(alreadyLogged)
Might I suggest a different approach?
why not make use of array.indexOf() ?
lets say you have two arrays to compare arrA & arrB; you can see if an item exists like so:
var index = arrA.indexOf(arrB[0]);
if(index == -1){
console.log('No Match');
}
else{
console.log('Match found');
}
no need for any preset flags to see if one array contains an element.
Hope it helps.
A bit more context:
var index = loggedOrderArray.indexOf(trackedOrderNum);
if(index == -1){
console.log('No Match');
// -1 basicaly means that there is not instance of trackedOrderNum in loggedOrderArray
}
else{
console.log('Match found');
}
What you are attempting appears to be reasonably simple. You are just overwhelming yourself with awkward flow control and bulky code.
As it stands, asynchronous flow isn't quite right chiefly due to parseString() not being promisified. A value returned from a raw nodeback won't propagate down a .then chain.
In addition, asynchronous flow will improve with :
application of Promise.all() up front to aggregate the two essential data-delivering promises csvShipPromise and findPromise.
the realisation that wholly synchronous steps in a promise chain can be merged with next step.
And, the bulk of the synchronous code will reduce by employing several Array methods:
Array.prototype.filter()
Array.prototype.map()
Array.prototype.includes()
Boiling it down to somewhere near the bare minimum, I get the following router.get() expression:
router.get('/', function(req, res, next) {
return Promise.all([csvShipPromise, findPromise])
.then([csvOrders, findData] => { // destructuring
let loggedOrderArr = csvOrders.map(order => order.OrderNumber);
let foundData = replaceAll(findData, '<', '<');
foundData = replaceAll(foundData, '>', '>');
return new Promise((resolve, reject) => { // promisify parseString() on the fly
parseString(foundData, (err, result) => {
if(err) reject(err);
else resolve(result['soap:Envelope']['soap:Body'][0].FindOrders[0].orders[0].order); // does this expression really return `orders` (plural)?
});
})
.then(orders => {
let trackArray = orders.filter(postObj => postObj.TrackingNumber[0].length > 1); // filter orders to eliminate those without a tracking number.
let loggedOrders = trackArray.filter(trackedItm => loggedOrderArr.includes(trackedItm.ReferenceNum));
// let unloggedOrders = trackArray.filter(trackedItm => !loggedOrderArr.includes(trackedItm.ReferenceNum));
res.send(loggedOrders); // or res.send(unloggedOrders), depending on what you want.
});
})
.catch(err => {
console.log(err);
res.error(err); // or similar
});
});
untested - I may have made mistakes, though hopefully ones that are simple to correct
Dears ,
How can i run promises in nodejs sequentially , in the following example am looping through array of hours then for each fetched hour get result from the database , the issue here : am getting results but i want it sequentially same order that i got hours .
angular.forEach(SharedVar.getCategories(), function (h) {
t = h.split('-', 2);
t = t[0];
RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {
$scope.answerdCallsTotalByHour.push(answerdTotal);
var d = SharedVar.getDataS();
d[count] = answerdTotal;
SharedVar.setDataS(d);
count++;
});
});
Thanks ,
var promise = Promise.resolve(); // make an empty promise in the way you do it with your promise library
angular.forEach(SharedVar.getCategories(), function (h) {
promise.then(function() {
return RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {});
});
});
The way to do it sequently would be to do one Request and do the next request inside the promise.
I think the better approach by far is to extend your SharedVar.setDataS(d) function in a way, that it does not depend on getting the data sequentially. Like having a SharedVar.setDataS(d, index) and using the config var in your $http.get (or whatever) functioncall inside your RESTApi to promote that index all the way to the promise.
If your RESTApi looks like this:
var RESTApi = {
getAnswerdCallsByHour : function(hour) {
var url = "bla.com/myservice?hour=" + hour;
return $http.get(url).data;
}
// Some other Methods...
Then you need a way to pass something to "reorder" your Data when it arrives asynchronously, this could be a index you count up or in your case maybe the hour Variable:
var RESTApi = {
getAnswerdCallsByHour : function(hour) {
var url = "bla.com/myservice?hour=" + hour;
var config = [];
config.hour = hour;
return $http.get(url, config); // Return the promise not just data or a specific field
}
// Some other Methods...
Now when your promise is fullfiled you can access your "hour" Variable like so:
var d = SharedVar.getDataS();
d[promise.config.hour] = promise.data;
SharedVar.setDataS(d);
Now you know what piece of data correlates to which request and you do not need to recieve Data in order. The last piece only works properly when hours runs sequential from 0 to 23, if that isn't the case you need to:
var RESTApi = {
getAnswerdCallsByHour : function(hour, index) {
var url = "bla.com/myservice?hour=" + hour;
var config = [];
config.index = index;
return $http.get(url, config);
}
// Some other Methods...
...
...
var d = SharedVar.getDataS();
d[promise.config.index] = promise.data;
SharedVar.setDataS(d);
Safari's answer is how I typically handle this. (Sorry, I don't have enough rep to comment yet...) You were experiencing problems with it because the example provided does not capture and use the new promise in subsequent loops. See my comments on the slightly modified version here:
var promise = Promise.resolve();
angular.forEach(SharedVar.getCategories(), function (h) {
t = h.split('-', 2);
t = t[0];
// You must capture the new promise here; the next loop will wait
// for the promise returned from getAnswerdCallsByHour to resolve.
promise = promise.then(function() {
// Halt downstream promises until this returned promises finishes
return RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {
$scope.answerdCallsTotalByHour.push(answerdTotal);
var d = SharedVar.getDataS();
d[count] = answerdTotal;
SharedVar.setDataS(d);
count++;
});
});
});
Hi I have a backbone web app using Jquery and NodeJs/mongo as the server side framework. I'm having problems with making a http get call with a foreah loop and the results of the get call being iteratively added to each row of the loop.
var eventid = this.model.get("_id");
var inPromise = $.get("/registrants/list?eventid="+eventid,null,null,"json").then(
function (result){
var temp;
var finalVal = '';
var tempfinalVal = "";
var loop = 0
percentage = 0;
$.each(result.registrants,function(index,registrant){
temp = JSON.parse(registrant.fields);
for (var key in temp) {
if(key =="Email"){
if(temp[key] != ""){
$.get("/stats/registrant?userid="+temp[key]+"&eventid="+eventid,null,null,"json").then(function(result2){
percentage = (result2.Stats.type ===undefined || result2.Stats.type ==null) ? "0": result2.Stats.type;
finalVal +=percentage+"\n";
}).fail(function(){
percentage = "0";
});
}
}else if(key =="eventid"){
loop++;
finalVal = finalVal.slice(0, - 1);
finalVal +='\n';
}
finalVal +=temp[key] + ',';
}
});
//promises.push(inPromise);
}
).done(function(finalVal){
$("#webcast-download-registrants-tn").attr("href",'data:text/csv;charset=utf-8;filename=registration.csv",'+encodeURIComponent(finalVal));
console.log("DONE");
}).fail(function(){
console.log("fail");
});
// promise.done(function () {
// console.log(" PROMISE DONE");
// });
So I have the loop through a collection and the last item of the docuemnt gets a content froma nother http call and when all is fone it will create a CSV file. The problem is that THE "DONE" text echos firts then the "CALL" text is displayed
Rick, your problem is not the simplest due to :
the need for nested asynchronous gets
the need to build each CSV data row partly synchronously, partly asynchronously.
the need for a mechanism to handle the fulfilment of multiple promises generated in the inner loop.
From what you've tried, I guess you already know that much.
One important thing to note is that you can't rely on for (var key in temp) to deliver properties in any particular order. Only arrays have order.
You might try something like this :
var url = "/stats/registrant",
data = { 'eventid': this.model.get('_id') },
rowTerminator = "\n",
fieldNames = ['firstname','lastname','email','company','score'];
function getScore(email) {
return $.get(url, $.extend({}, data, {'userid':email}), null, "json").then(function(res) {
return res.Stats ? res.Stats.type || 0 : 0;
}, function() {
//ajax failure - assume score == 0
return $.when(0);
});
}
$.get("/registrants/list", data, null, "json").then(function(result) {
var promises = [];//An array in which to accumulate promises of CSV rows
promises.push($.when(fieldNames)); //promise of CSV header row
if(result.registrants) {
$.each(result.registrants, function(index, registrant) {
if(registrant.fields) {
// Synchronously initialize row with firstname, lastname, email and company
// (omitting score for now).
var row = fieldNames.slice(0,-1).map(function(fieldName, i) {
return registrant.fields[fieldName] || '';
});
//`row` remains available to inner functions due to closure
var promise;
if(registrant.fields.Email) {
// Fetch the registrant's score ...
promise = getScore(registrant.fields.Email).then(function(score) {
//... and asynchronously push the score onto row
row.push(score);
return row;
});
} else {
//or synchronously push zero onto row ...
row.push(0);
//... and create a resolved promise
promise = $.when(row);
}
promises.push(promise);//Accumulate promises of CSV data rows (still in array form), in the correct order.
}
});
}
return $.when.apply(null, promises).then(function() {
//Join all the pieces, in nested arrays, together into one long string.
return [].slice.apply(arguments).map(function(row) {
return row.join(); //default glue is ','
}).join(rowTerminator);
});
}).done(function(str) {
$("#webcast-download-registrants-tn").attr("href",'data:text/csv;charset=utf-8;filename=registration.csv",'+encodeURIComponent(str));
console.log("DONE");
}).fail(function() {
console.log("fail");
});
partially tested
See comments in code for explanation and please ask if there's anything you don't follow.
I have seen Chaining an arbitrary number of promises in Q ; my question is different.
How can I make a variable number of calls, each of which returns asynchronously, in order?
The scenario is a set of HTTP requests, the number and type of which is determined by the results of the first HTTP request.
I'd like to do this simply.
I have also seen this answer which suggests something like this:
var q = require('q'),
itemsToProcess = ["one", "two", "three", "four", "five"];
function getDeferredResult(prevResult) {
return (function (someResult) {
var deferred = q.defer();
// any async function (setTimeout for now will do, $.ajax() later)
setTimeout(function () {
var nextResult = (someResult || "Initial_Blank_Value ") + ".." + itemsToProcess[0];
itemsToProcess = itemsToProcess.splice(1);
console.log("tick", nextResult, "Array:", itemsToProcess);
deferred.resolve(nextResult);
}, 600);
return deferred.promise;
}(prevResult));
}
var chain = q.resolve("start");
for (var i = itemsToProcess.length; i > 0; i--) {
chain = chain.then(getDeferredResult);
}
...but it seems awkward to loop through the itemsToProcess in that way. Or to define a new function called "loop" that abstracts the recursion. What's a better way?
There's a nice clean way to to this with [].reduce.
var chain = itemsToProcess.reduce(function (previous, item) {
return previous.then(function (previousValue) {
// do what you want with previous value
// return your async operation
return Q.delay(100);
})
}, Q.resolve(/* set the first "previousValue" here */));
chain.then(function (lastResult) {
// ...
});
reduce iterates through the array, passing in the returned value of the previous iteration. In this case you're returning promises, and so each time you are chaining a then. You provide an initial promise (as you did with q.resolve("start")) to kick things off.
At first it can take a while to wrap your head around what's going on here but if you take a moment to work through it then it's an easy pattern to use anywhere, without having to set up any machinery.
I like this way better:
var q = require('q'),
itemsToProcess = ["one", "two", "three", "four", "five"];
function getDeferredResult(a) {
return (function (items) {
var deferred;
// end
if (items.length === 0) {
return q.resolve(true);
}
deferred = q.defer();
// any async function (setTimeout for now will do, $.ajax() later)
setTimeout(function () {
var a = items[0];
console.log(a);
// pop one item off the array of workitems
deferred.resolve(items.splice(1));
}, 600);
return deferred.promise.then(getDeferredResult);
}(a));
}
q.resolve(itemsToProcess)
.then(getDeferredResult);
The key here is to call .then() on the deferred.promise with a spliced version of the array of workitems. This then gets run after the initial deferred promise resolves, which is in the fn for the setTimeout. In a more realistic scenario, the deferred promise would get resolved in the http client callback.
The initial q.resolve(itemsToProcess) kicks things off by passing in the work items to the first call of the work fn.
I added this in hopes it would help others.
Here is a concept of a state machine defined with Q.
Suppose you have the HTTP function defined, so it returns a Q promise object:
var Q_http = function (url, options) {
return Q.when($.ajax(url, options));
}
You can define a recursive function nextState as following:
var states = [...]; // an array of states in the system.
// this is a state machine to control what url to get data from
// at the current state
function nextState(current) {
if (is_terminal_state(current))
return Q(true);
return Q_http(current.url, current.data).then(function (result) {
var next = process(current, result);
return nextState(next);
});
}
Where function process(current, result) is a function to find out what the next step would be according to the current state and the result from the HTTP call.
When you use it, use it like:
nextState(initial).then(function () {
// all requests are successful.
}, function (reason) {
// for some unexpected reason the request sequence fails in the middle.
});
I propose another solutions, which looks easier to understand to me.
You do the same as you would when chaining promises directly:
promise.then(doSomethingFunction).then(doAnotherThingFunction);
If we put that into a loop, we get this:
var chain = Q.when();
for(...) {
chain = chain.then(functionToCall.bind(this, arg1, arg2));
};
chain.then(function() {
console.log("whole chain resolved");
});
var functionToCall = function(arg1, arg2, resultFromPreviousPromise) {
}
We use function currying to use multiple arguments. In our example
functionToCall.bind(this, arg1, arg2) will return a function with one argument: functionToCall(resultFromPreviousPromise)
You do not need to use the result from the previous promise.