Looping with Q promises in node.js - javascript

I'm very new to js and node coming from a java world. I threw a test program together based on the real program I'm writing. It's using the Q library in a node.js program.
The for loop is only acting on the last item in the array.. it's like all the other Q.promises are lost.
My output is:
registerELB
de-registerELB
resumeASG 4
started instance 4
resumeASG 4
started instance 4
resumeASG 4
started instance 4
resumeASG 4
started instance 4
autoshutdown complete.
Where as I expect it to be:
resumeASG 1
started instance 1
resumeASG 2
started instance 2
resumeASG 3
started instance 3
resumeASG 4
started instance 4
registerELB
de-registerELB
autoshutdown complete.
what's going on in the loop? Are the promises getting lost?
why is registerELB and de-registerELB happening before my first .then()
var Q = require('q');
var startedInstances = [];
test();
function test() {
getInstances()
.then(function(data) {
var promise = Q.defer();
for(var x = 0; x < data.length; x++) {
var somedata = data[x];
console.log(somedata);
startOrStop(somedata)
.then(function (updateStatus) {
var promiseinner = Q.defer();
console.log(somedata);
if(updateStatus == 'start') {
return Q.nfcall(resumeASG, somedata)
.then(Q.nfcall(startInstance, somedata));
} else if(updateStatus == 'stop') {
return Q.nfcall(suspendASG, somedata)
.then(Q.nfcall(stopInstance, somedata));
}
promiseinner.resolve('complete');
return promiseinner.promise;
})
}
promise.resolve('successful');
return promise.promise;
}, function(error) {
console.log('Failed to get instance data ' + error);
//context.fail();
}).then(function(data) {
return registerELB();
}, function(error) {
console.log('Failed to Re-register ELB ' + error);
//context.fail();
}).then(function(data) {
console.log('autoshutdown complete.')
//context.succeed(data);
}, function(error) {
console.log('autoshutdown failed to complete. ' + error)
//context.fail();
}).done();
};
function getInstances() {
var promise = Q.defer();
startedInstances.push(1);
startedInstances.push(2);
startedInstances.push(3);
startedInstances.push(4);
promise.resolve(startedInstances);
return promise.promise;
}
function suspendASG (asg) {
var promise = Q.defer();
console.log('suspendASG ' + asg);
promise.resolve(asg);
return promise.promise;
}
function resumeASG (asg) {
var promise = Q.defer();
console.log('resumeASG ' + asg);
promise.resolve(asg);
return promise.promise;
}
function registerELB() {
var promise = Q.defer();
console.log('registerELB ');
console.log('de-registerELB ');
promise.resolve('success elb');
return promise.promise;
}
function startInstance(instanceId) {
var promise = Q.defer();
console.log('started instance ' + instanceId);
promise.resolve(instanceId);
return promise.promise;
}
function stopInstance(instanceId) {
var promise = Q.defer();
console.log('stopped instance ' + instanceId);
promise.resolve(instanceId);
return promise.promise;
}
function startOrStop (instance) {
var promise = Q.defer();
promise.resolve('start');
return promise.promise;
}

This is a classic issue with a for loop and any async operation inside the loop. Because your async operations inside the for loop complete some time LATER after the for loop has already finished, any variables set inside your for loop will have the values at the end of the loop when any of your async operations completes.
In your particular case, the somedata variable is a problem here. You can generally work-around this issue by using a closure so that each invocation of your async operations has it's own function and thus it's own state that uniquely survives until the async operation has completed.
It looks to me like you may also have other synchronization issues going on here since your for loop is running all your operations in parallel and you have no link between your inner async operations and the outer promise you are resolving, so I suspect there are other problems here too.
But, if you just change your for loop to a .forEach() loop, then each invocation of the loop will have it's own unique state. I honestly can't say that there aren't other issues in this code (since I have no way of testing it), but this should at least solve one issue related to the for loop:
test();
function test() {
getInstances()
.then(function(data) {
var promise = Q.defer();
// -------- Change made here -------------
// change for loop to .forEach() loop here
// to create closure so your async operations
// each can access their own data
data.forEach(function(somedata) {
console.log(somedata);
startOrStop(somedata)
.then(function (updateStatus) {
var promiseinner = Q.defer();
console.log(somedata);
if(updateStatus == 'start') {
return Q.nfcall(resumeASG, somedata)
.then(Q.nfcall(startInstance, somedata));
} else if(updateStatus == 'stop') {
return Q.nfcall(suspendASG, somedata)
.then(Q.nfcall(stopInstance, somedata));
}
promiseinner.resolve('complete');
return promiseinner.promise;
})
});
promise.resolve('successful');
return promise.promise;
}, function(error) {
console.log('Failed to get instance data ' + error);
//context.fail();
}).then(function(data) {
return registerELB();
}, function(error) {
console.log('Failed to Re-register ELB ' + error);
//context.fail();
}).then(function(data) {
console.log('autoshutdown complete.')
//context.succeed(data);
}, function(error) {
console.log('autoshutdown failed to complete. ' + error)
//context.fail();
}).done();
};
Also, do you realize that your inner promises (inside the .forEach() loop) are not linked at all to the outer promise? So, getInstances() will resolve its promise, long before the inner promises in the .forEach() loop are done? This looks broken to me. I don't know exactly what to recommend because I don't know which operations you want to run in parallel and which should be in series. You may need to do a Promise.all() that collects all the promises from the for loop and then use that to resolve your outer promise.

Related

How to cancel a promise with $q in angular js

I have a service below. I will call this service every time when I open a model and when I close the model and then open another one the previous values are getting reflected and in this case I want to cancel the promise every time I close the model.
I have tried the following code,
Model closing.js
$scope.closeButton = function() {
DetailDataSvc.storeDefer().resolve()
}
My Service, (DetailDataSvc)
self.storeDefer = function() {
return self.deferReturn;
};
self.getDetailReportData = function(postData, functionName) {
var promises = {};
var d = $q.defer(),
metricDataType;
self.deferReturn = $q.defer();
promises = {
detailReport: metricDataType,
recommendedMetrics: DataSvc.getData(_logPrefix + functionName, recommendedMetricUrl),
metricInfo: DataSvc.getData(_logPrefix + functionName, metricInfoUrl)
};
$q.all(promises).then(function(res) {
$log.debug(_logPrefix + 'getDetailReportData(). Called from %s. $q.all Response (raw): ', functionName, res);
else {
if (response && !_.isEmpty(_.get(response, 'largeCard.chartData.dataValues.rows')) && response.overlayEnabled) {
self.getMetricOverLay(pdata, functionName).then(function(overlayData) {
response.largeCard.chartData.overlay = overlayData;
d.resolve(response);
}, function(msg, code) {
d.reject(msg);
$log.error(_logPrefix + 'getDetailReportData(). Error code: %s. Error: ', code, msg);
});
} else {
d.resolve(response);
}
}
}, function(msg, code) {
d.reject(msg);
$log.error(_logPrefix + 'getDetailReportData(). Error code: %s. Error: ', code, msg);
});
return d.promise;
};
Can anyone please help me whether the process I followed is the right one.
What you have attempted could be made to work but it's best fixed by racing the promise returned by $q.all() against a rejectable Deferred (ie. a Deferred, of which a reference is kept to its reject method), thus avoiding the deferred anti-pattern.
self.getDetailReportData = function(postData, functionName) {
var metricDataType = ......; // ???
var d = $q.defer();
// cancel previous
if(self.cancelDetailReport) {
self.cancelDetailReport(new Error('previous getDetailReportData() cancelled'));
}
// keep a reference to the deferred's reject method for next time round.
self.cancelDetailReport = d.reject;
var promises = {
'detailReport': metricDataType,
'recommendedMetrics': DataSvc.getData(_logPrefix + functionName, recommendedMetricUrl),
'metricInfo': DataSvc.getData(_logPrefix + functionName, metricInfoUrl)
};
// Race aggregated `promises` against `d.promise`, thus providing the required cancellation effect.
return $q.race([$q.all(promises), d.promise])
.then(function(response) {
// arrive here only if all promises resolve and d.reject() has not been called.
$log.debug(_logPrefix + 'getDetailReportData(). Called from %s. $q.all Response (raw): ', functionName, response);
if (response && !_.isEmpty(_.get(response, 'largeCard.chartData.dataValues.rows')) && response.overlayEnabled) {
return self.getMetricOverLay(pdata, functionName)
.then(function(overlayData) {
response.largeCard.chartData.overlay = overlayData;
return response;
});
} else {
return response;
}
})
.catch(function(msg, code) { // signature?
// all error cases including cancellation end up here.
var message = _logPrefix + `getDetailReportData(). Error: (${code}): ${msg}`; // or similar
$log.error(message);
throw new Error(message); // see https://stackoverflow.com/a/42250798/3478010
});
};
Notes:
$q.race() is transparent to whichever promise wins the race, and opaque to the other. So, if the d is rejected before the promise returned by $q.all() settles, then d will win out; response handling will not happen and d's rejection will fall through to the .catch() clause. Alternatively, if the promise returned by $q.all(promises) wins out then flow will follow that promise's success path (ie response handling) or possibly its error path (which will drop through to the .catch() clause).
Not too sure about the signature of the .catch() callback. You would normally expect it to accept a single error argument.
Assign already created deferred.
Try and change this line:
self.deferReturn = $q.defer();
self.deferReturn = d;

node.js promises not forcing order execution of functions

I have three functions that I want to use promises to force them to execute in order.
function 1 sends a http request, fetches JSON data and saved it to a file
function 2 loops through that file and updates the database according the difference values/values missing
function 3 will loop through the newly updated database and create a 2nd json file.
Currently function 1 works perfectly on its own with a setInterval of 30 minutes.
I want to start function 2 when function 1 has finished. then function 3 after function 2 has finished.
Using promises I am trying to attach function 2 to a simple finished log to understand how to use promises but not getting much success. The items from the for loop log but my Finished/err log before my for loop which shouldn't be happening. Any suggestions?
function readJson() {
return new Promise(function() {
fs.readFile(__dirname + "/" + "bitSkin.json", 'utf8', function read(err, data) {
if (err) { throw err; }
var bitCon = JSON.parse(data);
for(var i=0; i<7; i++) { //bitCon.prices.length; i++) {
var price = bitCon.prices[i].price
var itemName = bitCon.prices[i].market_hash_name;
(function() {
var iNameCopy = itemName;
var priceCopy = price;
logger.info(iNameCopy);
}());
}
});
});
};
function fin() {
logger.info("Finished");
}
readJson().then(fin(), console.log("err"));
Promises have no magical powers. They don't magically know when async code inside them is done. If you create a promise, you yourself have to resolve() or reject() it when the async code has an error or completes.
Then, in addition, you have to pass a function reference to a .then() handler, not the result of executing a function. .then(fin()) will call fin() immediately and pass it's return value to .then() which is not what you want. You want something like .then(fin).
Here's how you can resolve and reject the promise you created:
function readJson() {
return new Promise(function(resolve, reject) {
fs.readFile(__dirname + "/" + "bitSkin.json", 'utf8', function read(err, data) {
if (err) { return reject(err); }
var bitCon = JSON.parse(data);
for(var i=0; i<7; i++) { //bitCon.prices.length; i++) {
var price = bitCon.prices[i].price
var itemName = bitCon.prices[i].market_hash_name;
(function() {
var iNameCopy = itemName;
var priceCopy = price;
logger.info(iNameCopy);
}());
}
resolve(bitCon);
});
});
};
And, you could use that like this:
function fin() {
logger.info("Finished");
}
readJson().then(fin, function(err) {
console.log("err", err)
});
Summary of changes:
Added resolve, reject arguments to Promise callback so we can use them
Called reject(err) when there's an error
Called resolve() when the async code is done.
Passed a function reference for both .then() handlers.
FYI, when creating a promise wrapper around an async function, it is generally better to wrap just the function itself. This makes the wrapper 100% reusable and puts more of your code in the promise architecture which generally streamlines things and makes error handling easier. You could fix things up that way like this:
fs.readFilePromise = function(file, options) {
return new Promise(function(resolve, reject) {
fs.readFile(file, options, function(err, data) {
if (err) return reject(err);
resolve(data);
});
});
};
function readJson() {
return fs.readFilePromise(__dirname + "/" + "bitSkin.json", 'utf8').then(function(data) {
var bitCon = JSON.parse(data);
bitCon.prices.forEach(function(item) {
logger.info(item.market_hash_name);
});
return bitCon;
});
}

Why does my Promise Chain not work in this nested way?

I want to chain 4 functions in a Promise chain like so:
function1 -> function2 -> function3 -> function4
My Promise chain
if ($location.$$url !== "/dashboard") {
vm.customURL = true;
// (1) Set root vars & Rebuild tickerTagsContainer:
var promise = TagFactory.buildUrlObject($location.$$url).then(function() {
console.log('TagFactory.buildUrlObject PROMISE returned');
}).then(function() {
console.log('(2) Re-display tags in viewHeader');
// (2) Re-display tags in viewHeader:
viewHeader = ScopeFactory.getScope('viewHeader');
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
}).then(function() {
// (3) Reselect timeSpan:
console.log('(3) Reselect timeSpan');
viewHeader.vh.toggleTimeSpan(vm.timeSpan);
// (4) Refresh URL:
console.log('(4) Refresh URL');
ViewFactory.remakeViewObject($location.$$url);
});
}
The resulting console.logs:
^ Note I never see this log:
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
Ideally I want to place my (3) function inside that, then chain my (4) like so:
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
console.log('(3) Reselect timeSpan');
viewHeader.vh.toggleTimeSpan(vm.timeSpan).then(function() {
console.log('(4) Refresh URL');
ViewFactory.remakeViewObject($location.$$url);
});
});
However I never see the console.log from the .then function for displayViewHeaderTags
Here is what my displayViewHeaderTags looks like:
function displayViewHeaderTags() {
vm.viewTickerTags = [];
vm.viewTickerTags = TagFactory.retrieveTickerTags('all');
var deferred = $q.defer();
var tikObjs = vm.viewTickerTags.map(function(el) { return el.ticker; });
var tagObjs = vm.viewTickerTags.map(function(el) { return el.tags; });
var tags = _.flatten(tagObjs);
// forEach loops up to 3 times:
tags.forEach(function(tag, i) {
vm.viewTags = [];
ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
if (i === tags.length) {
deferred.resolve();
}
});
});
return deferred.promise;
}
Inside my displayViewHeaderTags function I hit a loop which will run up to 3 times, after it's done getting data, it will fill up and array then calls deffered.resolve. then returns it return deferred.promise;
So why do I never see this log? console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
Your i is never the same as the length, because the i variable starts at zero (array indexes start at zero). Which means the if you have an array with length = 2, your i values will be 0 and 1 respectively. It will never equal to zero. Basically, you would want the condition to be:
vm.viewTags.push(data.data.ticker_tag);
if (i + 1 === tags.length) {
deferred.resolve();
}
Anyway, using defer() is a code smell.
A more elegant way of doing it would be using $q.all
var allPromises = [];
var promise;
tags.forEach(function(tag) {
vm.viewTags = [];
promise = ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
});
// Create an array of promises, one promise for each request
allPromises.push( promise );
});
// Return a new promise that will only be resolved
// when all the promises of the array `allPromises` are resolved,
// or is rejected when one of them is.
return $q.all( allPromises );
Your chain is not really doing anything since you're not returning a promise from any of those anonymous functions. You're not seeing that log probably because ApiFactory.getTagDataSilm is failing or never resolving. Try adding an error handler into your flow.
if ($location.$$url !== "/dashboard") {
vm.customURL = true;
// (1) Set root vars & Rebuild tickerTagsContainer:
var promise = TagFactory.buildUrlObject($location.$$url).then(function() {
console.log('TagFactory.buildUrlObject PROMISE returned');
}).then(function() {
console.log('(2) Re-display tags in viewHeader');
// (2) Re-display tags in viewHeader:
viewHeader = ScopeFactory.getScope('viewHeader');
return viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
}).then(function() {
// (3) Reselect timeSpan:
console.log('(3) Reselect timeSpan');
return viewHeader.vh.toggleTimeSpan(vm.timeSpan);
}).then(function() {
// (4) Refresh URL:
console.log('(4) Refresh URL');
return ViewFactory.remakeViewObject($location.$$url);
}).catch(function(error) {
console.log('Something failed', error);
});
}
Within displayViewHeaderTags, you can use $q.all, so that rejections are handled for you:
// forEach loops up to 3 times:
vm.viewTags = [];
return $q.all(_.map(tags, function(tag) {
return ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
});
}));

How to assign multiple onError functions to a promise (returned by angular's $http.post)

My AngularJS code needs to chain multiple onSuccess, onError functions to a promise returned by $http.post
var promise = $http.post(url);
promise
.then(
/*success 1*/function () { console.log("success 1"); },
/*error 1*/function () { console.log("error 1"); })
.then(
/*success 2*/function () { console.log("success 2"); },
/*error 2*/function () { console.log("error 2"); });
The problem with above code is that it prints error 1 > success 2 when the HTTP response fails instead of error 1 > error 2.
I did some research on stackoverflow and found that when you have access to $q you can just do $q.reject() in error 1 to trigger error 2 but in my case i only have access to the promise returned by $http.post. So what do I do?
P.S. Of course, I can call error2() from inside of error 1 but i want to chain them because it looks more readable and extensible.
Any ideas?
Returning a value (or returning no value) from a success/error handler will resolve the promise for the next then block in the chain. To propagate the rejection, return $q.reject():
var promise = $http.post(url);
promise
.then(
/*success 1*/function () { console.log("success 1"); },
/*error 1*/function () { console.log("error 1"); return $q.reject();})
.then(
/*success 2*/function () { console.log("success 2"); },
/*error 2*/function () { console.log("error 2"); });
Your question stems from some misunderstanding of what promises enable - namely, async code composition that parallels that of a synchronous code with try/catch, with proper exception handling.
I am specifically referring to your statement:
"but i want to chain them because it looks more readable and extensible."
as the source of misunderstanding of chaining.
If your example was synchronous (assuming all async calls were blocking), this is likely what you would have wanted to do:
try {
var data = $http.post(url); // blocking
var res1 = doSuccess1(data);
var ret = doSuccess2(res1);
}
catch(e){
errorHandler1(e);
errorHandler2(e);
}
And not this:
try {
try {
var data = $http.post(url);
var res1 = doSuccess1(data);
} catch (e) {
errorHandler1(e);
// throw ""; // this is what returning $q.reject would have done - a rethrow
}
} catch (e) {
errorHandler2(e);
}
var ret = doSuccess2(res1);
which is what you would have achieved with your chaining. In other words, nested try/catch and unhandled exception in doSuccess2.
The following is the async parallel of the first approach:
var ret;
$http.post(url)
.then(function(data){
var res1 = doSuccess1(data);
ret = doSuccess2(res1);
}
.catch(function(e){ // or .then(null, handler)
doError1(e);
doError2(e);
})
And if one of doSuccessN functions were also async:
var ret;
$http.post(url)
.then(doSuccess1Async)
.then(function(res1){
ret = doSuccess2(res1);
}
.catch(function(e){ // or .then(null, handler)
doError1(e);
doError2(e);
})
Just wrap the handlers in a function, in the success / error handler parameters:
var promise = $http.post(url);
promise
.then(function(argX, argY){
success1(argX, argY);
success2(argX, argY);
},
function(argX, argY){
error1(argX, argY);
error2(argX, argY);
});

Multiple Promise Chains in Single Function

I have some code that will dynamically generate an AJAX request based off a scenario that I'm retrieving via an AJAX request to a server.
The idea is that:
A server provides a "Scenario" for me to generate an AJAX Request.
I generate an AJAX Request based off the Scenario.
I then repeat this process, over and over in a Loop.
I'm doing this with promises here: http://jsfiddle.net/3Lddzp9j/11/
However, I'm trying to edit the code above so I can handle an array of scenarios from the initial AJAX request.
IE:
{
"base": {
"frequency": "5000"
},
"endpoints": [
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/alvarengarichard",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
},
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/dkang",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
}
]
This seems like it would be straight forward, but the issue seems to be in the "waitForTimeout" function.
I'm unable to figure out how to run multiple promise chains. I have an array of promises in the "deferred" variable, but the chain only continues on the first one--despite being in a for loop.
Could anyone provide insight as to why this is? You can see where this is occuring here: http://jsfiddle.net/3Lddzp9j/10/
The main problems are that :
waitForTimeout isn't passing on all the instructions
even if waitForTimeout was fixed, then callApi isn't written to perform multiple ajax calls.
There's a number of other issues with the code.
you really need some data checking (and associated error handling) to ensure that expected components exist in the data.
mapToInstruction is an unnecessary step - you can map straight from data to ajax options - no need for an intermediate data transform.
waitForTimeout can be greatly simplified to a single promise, resolved by a single timeout.
synchronous functions in a promise chain don't need to return a promise - they can return a result or undefined.
Sticking with jQuery all through, you should end up with something like this :
var App = (function ($) {
// Gets the scenario from the API
// sugar for $.ajax with GET as method - NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario2');
};
var checkData = function (data) {
if(!data.endpoints || !data.endpoints.length) {
return $.Deferred().reject('no endpoints').promise();
}
data.base = data.base || {};
data.base.frequency = data.base.frequency || 1000;//default value
};
var waitForTimeout = function(data) {
return $.Deferred(function(dfrd) {
setTimeout(function() {
dfrd.resolve(data.endpoints);
}, data.base.frequency);
}).promise();
};
var callApi = function(endpoints) {
console.log('Calling API with given instructions ...');
return $.when.apply(null, endpoints.map(ep) {
return $.ajax({
type: ep.method,
dataType: ep.type,
url: ep.endpoint
}).then(null, function(jqXHR, textStatus, errorThrown) {
return textStatus;
});
}).then(function() {
//convert arguments to an array of results
return $.map(arguments, function(arg) {
return arg[0];
});
});
};
var handleResults = function(results) {
// results is an array of data values/objects returned by the ajax calls.
console.log("Handling data ...");
...
};
// The 'run' method
var run = function() {
getScenario()
.then(checkData)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(null, function(reason) {
console.error(reason);
})
.then(run);
};
return {
run : run
}
})(jQuery);
App.run();
This will stop on error but could be easily adapted to continue.
I'll try to answer your question using KrisKowal's q since I'm not very proficient with the promises generated by jQuery.
First of all I'm not sure whether you want to solve the array of promises in series or in parallel, in the solution proposed I resolved all of them in parallel :), to solve them in series I'd use Q's reduce
function getScenario() { ... }
function ajaxRequest(instruction) { ... }
function createPromisifiedInstruction(instruction) {
// delay with frequency, not sure why you want to do this :(
return Q.delay(instruction.frequency)
.then(function () {
return this.ajaxRequest(instruction);
});
}
function run() {
getScenario()
.then(function (data) {
var promises = [];
var instruction;
var i;
for (i = 0; i < data.endpoints.length; i += 1) {
instruction = {
method: data.endpoints[i].method,
type: data.endpoints[i].type,
endpoint: data.endpoints[i].endPoint,
frequency: data.base.frequency
};
promises.push(createPromisifiedInstruction(instruction));
}
// alternative Q.allSettled if all the promises don't need to
// be fulfilled (some of them might be rejected)
return Q.all(promises);
})
.then(function (instructionsResults) {
// instructions results is an array with the result of each
// promisified instruction
})
.then(run)
.done();
}
run();
Ok let me explain the solution above:
first of all assume that getScenario gets you the initial json you start with (actually returns a promise which is resolved with the json)
create the structure of each instruction
promisify each instruction, so that each one is actually a promise whose
resolution value will be the promise returned by ajaxRequest
ajaxRequest returns a promise whose resolution value is the result of the request, which also means that createPromisifiedInstruction resolution value will be the resolution value of ajaxRequest
Return a single promise with Q.all, what it actually does is fulfill itself when all the promises it was built with are resolved :), if one of them fails and you actually need to resolve the promise anyways use Q.allSettled
Do whatever you want with the resolution value of all the previous promises, note that instructionResults is an array holding the resolution value of each promise in the order they were declared
Reference: KrisKowal's Q
Try utilizing deferred.notify within setTimeout and Number(settings.frequency) * (1 + key) as setTimeout duration; msg at deferred.notify logged to console at deferred.progress callback , third function argument within .then following timeout
var App = (function ($) {
var getScenario = function () {
console.log("Getting scenario ...");
return $.get("http://demo3858327.mockable.io/scenario2");
};
var mapToInstruction = function (data) {
var res = $.map(data.endpoints, function(settings, key) {
return {
method:settings.method,
type:settings.type,
endpoint:settings.endPoint,
frequency:data.base.frequency
}
});
console.log("Instructions recieved:", res);
return res
};
var waitForTimeout = function(instruction) {
var res = $.when.apply(instruction,
$.map(instruction, function(settings, key) {
return new $.Deferred(function(dfd) {
setTimeout(function() {
dfd.notify("Waiting for "
+ settings.frequency
+ " ms")
.resolve(settings);
}, Number(settings.frequency) * (1 + key));
}).promise()
})
)
.then(function() {
return this
}, function(err) {
console.log("error", err)
}
, function(msg) {
console.log("\r\n" + msg + "\r\nat " + $.now() + "\r\n")
});
return res
};
var callApi = function(instruction) {
console.log("Calling API with given instructions ..."
, instruction);
var res = $.when.apply(instruction,
$.map(instruction, function(request, key) {
return request.then(function(settings) {
return $.ajax({
type: settings.method,
dataType: settings.type,
url: settings.endpoint
});
})
})
)
.then(function(data) {
return $.map(arguments, function(response, key) {
return response[0]
})
})
return res
};
var handleResults = function(data) {
console.log("Handling data ..."
, JSON.stringify(data, null, 4));
return data
};
var run = function() {
getScenario()
.then(mapToInstruction)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(run);
};
return {
// This will expose only the run method
// but will keep all other functions private
run : run
}
})($);
// ... And start the app
App.run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
jsfiddle http://jsfiddle.net/3Lddzp9j/13/
You have a return statement in the loop in your waitForTimeout function. This means that the function is going to return after the first iteration of the loop, and that is where you are going wrong.
You're also using the deferred antipattern and are using promises in places where you don't need them. You don't need to return a promise from a then handler unless there's something to await.
The key is that you need to map each of your instructions to a promise. Array#map is perfect for this. And please use a proper promise library, not jQuery promises (edit but if you absolutely must use jQuery promises...):
var App = (function ($) {
// Gets the scenario from the API
// NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario');
};
// mapToInstructions is basically unnecessary. each instruction does
// not need its own timeout if they're all the same value, and you're not
// reshaping the original values in any significant way
// This wraps the setTimeout into a promise, again
// so we can chain it
var waitForTimeout = function(data) {
var d = $.Deferred();
setTimeout(function () {
d.resolve(data.endpoints);
}, data.base.frequency);
return d.promise();
};
var callApi = function(instruction) {
return $.ajax({
type: instruction.method,
dataType: instruction.type,
url: instruction.endPoint
});
};
// Final step: call the API from the
// provided instructions
var callApis = function(instructions) {
console.log(instructions);
console.log('Calling API with given instructions ...');
return $.when.apply($, instructions.map(callApi));
};
var handleResults = function() {
var data = Array.prototype.slice(arguments);
console.log("Handling data ...");
};
// The 'run' method
var run = function() {
getScenario()
.then(waitForTimeout)
.then(callApis)
.then(handleResults)
.then(run);
};
return {
run : run
}
})($);
App.run();

Categories

Resources