in my project I'm not using callbacks instead I'm trying to use $.Deferred to have uniform logic across all application, I have lots places in my code where I do something like the following:
function someMagicHandler(request) {
var me = this;
var sandbox = me.getSandbox();
var options = request.options;
var deferred = request.deferred;
var result = [];
var databaseOperation = sandbox.database.all('records').done(function (records) {
result.concat(records);
deferred.notify(records);
});
var serverResponse;
var serverOperation = sandbox.server.requestRecords(options).then(function (response) {
// First we are trying to save received records to database
serverResponse = response;
result.concat(response.Records);
deferred.notify(response.Records);
return sandbox.database.put('records', response.Records);
}).done(function() {
sandbox.storage.setTimestamp('records', new Date(serverResponse.Timestamp));
});
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
}
In this code I'm personally don't like one of the last lines:
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
Is there a standard way to express:
$.when(databaseOperation, serverOperation).then(deferred);
which would essentially mean:
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject, deferred.notify);
Edit
I've investigated this problem deeper, and it seems that below solution is ok only when you are not relying on deferred.progress() which has no memory and therefore will not return any data in case when subsequent async operation is complete synchronously.
Summary
If you are using $.Deferred() as a callback (i.e. when you rely on notify or progress functions and in that case you need pass it as an argument) than you will be obligated to use the ugly
blahblahblah.then(deferred.resolve, deferred.reject, deferred.notify)
You can just replace this
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
with this:
request.deferred = $.when(databaseOperation, serverOperation);
And delete all references to the variable deferred because $.when already creates a promise for you with (as far as I read the manual).
Related
I have an Angularjs project that uses Restangular to access the database. I have three layers of data (say mydata, mysubdata, mysubsubdata) and there is a one-to-many relationship between each layer. My problem is that, for my display, I need to concatenate the mysubsubdata to the mysubdata. When I try to get data back from the database, I am hitting a complaint in the compiler that says I can't have a function in a loop. Here is what I am trying to do:
DataService.one(mydata.id).getList('mysubdata')
.then(function(data) {
var dataList = data;
for (returnedData in dataList) {
DataService.one(mydata.id).one('mysubdata',returnedData.id).getList('mysubsubdata')
.then(returnedSubData) {
dataList = angular.extend(dataList, returnedSubData);
});
}
});
All the examples I've found have loops inside the .then function or are trying to get a bunch of promises back first. I don't think those apply. I'm still pretty new to Angular, so I may be flailing a bit. Not sure about the extend either, but that's likely a separate question.
Edit: I suspect this should be done with a $q.all but haven't grasped the method yet.
Adding a then() method inside your loop won't work because of the simple reason that loop does not wait for the promises to be resolved. You can achieve this using recursive method.
var myCustomData = null,
dataList = null,
dataListCounter = 0;
DataService.one(mydata.id).getList('mysubdata')
.then(function (data){
dataList = data;
myCustomData = mydata;
$scope.getSubSubData();
});
$scope.getSubSubData = function () {
if (dataList.length >= dataListCounter)
return;
DataService.one(myCustomData.id).one('mysubdata',dataList[dataListCounter].id).getList('mysubsubdata')
.then(function (returnedSubData) {
dataList = angular.extend(dataList, returnedSubData);
dataListCounter++;
$scope.getSubSubData();
});
};
Please let me know if this helps!
Minor corrections to #Anadi Sharma's response.
$scope.getSubSubData = function () {
if (dataList.length == dataListCounter)
return;
DataService.one(myCustomData.id).one('mysubdata',dataList[dataListCounter].id).getList('mysubsubdata')
.then(function (returnedSubData) {
dataList[dataListCounter].mysubsubdata = returnedSubData;
dataListCounter++;
$scope.getSubSubData();
});
};
Note that I then use a filter when I display the data to concatenate the subsubdata values.
I'm using the axios promise library, but my question applies more generally I think. Right now I'm looping over some data and making a single REST call per iteration.
As each call completes I need to add the return value to an object. At a high level, it looks like this:
var mainObject = {};
myArrayOfData.forEach(function(singleElement){
myUrl = singleElement.webAddress;
axios.get(myUrl)
.then(function(response) {
mainObject[response.identifier] = response.value;
});
});
console.log(convertToStringValue(mainObject));
What's happening of course is when I call console.log the mainObject doesn't have any data in it yet, since axios is still reaching out. What's a good way of dealing with this situation?
Axios does have an all method along with a sister spread one, but they appear to be of use if you know ahead of time how many calls you'll be making, whereas in my case I don't know how many loop iterations there will be.
You need to collect all of your promises in an array and then use Promise.all:
// Example of gathering latest Stack Exchange questions across multiple sites
// Helpers for example
const apiUrl = 'https://api.stackexchange.com/2.2/questions?pagesize=1&order=desc&sort=activity&site=',
sites = ['stackoverflow', 'ubuntu', 'superuser'],
myArrayOfData = sites.map(function (site) {
return {webAddress: apiUrl + site};
});
function convertToStringValue(obj) {
return JSON.stringify(obj, null, '\t');
}
// Original question code
let mainObject = {},
promises = [];
myArrayOfData.forEach(function (singleElement) {
const myUrl = singleElement.webAddress;
promises.push(axios.get(myUrl));
});
Promise.all(promises).then(function (results) {
results.forEach(function (response) {
const question = response.data.items[0];
mainObject[question.question_id] = {
title: question.title,
link: question.link
};
});
console.log(convertToStringValue(mainObject));
});
<script src="https://unpkg.com/axios#0.19.2/dist/axios.min.js"></script>
It's described in axios docs (Performing multiple concurrent requests section).
Before May 2020 it was possible to do with axios.all(), which is now deprecated.
My program downloads a large amount of data, processes it, and makes it available through a returned function. The program gets ahread of the download, so I am adding promises to make it wait for the data to arrive.
function dataSource(...) {
var _data = null;
// download: a promise that returns data for the _data object.
let download = function() { ... }
return function(...) {
if (!_data) {
download(...).then(data => _data = data).done();
}
var datum = _data[key];
var outbound = doSomethingWithData(datum);
return outbound;
}
}
My code is structured like this because the function that Engine returns makes my code very neat.
var generate = dataSource(param1,param2);
var fullName = generate("malename")+" "+generate("malename")+" "+generate("surname");
The specific requirements are:
Download the data only once.
Query the data by key any number of times without downloading the data again.
Do not change the existing interface.
I could have dataSource return a promise rather than a function. I know what the pattern for using promises looks like. But that will force me to rewrite the code that consumes this function. This pattern is used extensively throughout the code, and changing it isn't an acceptable solution.
How can I structure this to ensure that my function doesn't return until it has the data, without returning the promise?
This should fix it
function dataSource(){
return function(){
return download(...).then(data=>doSomethingWithData(data[key]));
};
}
var generate = dataSource();
Promise
.all(["malename","malename","surname"].map((name)=>return generate(name)))
.spread((name1,name2,name3)=>{
return [name1,name2,name2].join(" ");
});
the spread is not needed, but it helps for illustration purposes
Have dataSource return a promise rather than the data function. The revised dataSource looks like this:
function dataSource(...) {
var _data = null;
// download: a promise that returns data for the _data object.
let download = function() { ... }
function _generate(...) {...}
return download(group,subgroup,options).then(data => _data = data).then(() => _generate);
}
Then, where the code is consumed, get the generate function from the returned promise:
let generate = function() {};
dataSource.then(fn => generate = fn).done();
I'm trying to execute several async requests and trying to get the output using promises.
If I've multiple requests queued up the Q.all(promises).then () function doesn't seem to be working. For a single request the promises are all resolved. The sample code is here.
var request = require('request');
var Q = require('q');
var sites = ['http://www.google.com', 'http://www.example.com', 'http://www.yahoo.com'];
// var sites = ['http://www.google.com']
var promises = [];
for (site in sites) {
var deferred = Q.defer();
promises.push(deferred.promise);
options = {url: sites[site]};
request(options, function (error, msg, body) {
if (error) {
deferred.reject();
}
deferred.resolve();
});
}
Q.all(promises).then (function () {
console.log('All Done');
});
What am I doing wrong here ?
Surya
Here is what I'd do in the scenario, this is the whole code:
var Q = require('q');
var request = Q.nfbind(require('request'));
var sites = ['http://www.google.com', 'http://www.example.com', 'http://www.yahoo.com'];
var requests = sites.map(request);
Q.all(requests).then(function(results){
console.log("All done") // you can access the results of the requests here
});
Now for the why:
Always promisify at the lowest level possible, promisify the request itself and not the speicific requests. Prefer automatic promisification to manual promisification to not make silly errors.
When working on collections - using .map is easier than manually iterating them since we're producing exactly one action per URL here.
This solution is also short and requires minimal nesting.
Don't use for..in to iterate over arrays. What this actually does is set site to 0, 1, 2 and this just doesn't work very well. Use some other form of iteration like a regular for loop, or Array.prototype.forEach
sites.forEach(function (site) {
var deferred = Q.defer();
promises.push(deferred.promise);
options = {url: site};
The problem is that you're changing the value of deferred on every tick of your for loop. So, the only promise which is actually resolved in your example is the last one.
To fix it you should store the value of deferred in some context. The easiest way to do so is to use Array.prototype.forEach() method instead of for loop:
sites.forEach(function (site){
var deferred = Q.defer();
var options = {url: sites[site]};
promises.push(deferred.promise);
request(options, function (error, msg, body) {
if (error) {
deferred.reject();
}
deferred.resolve();
});
})
And you also missed var declaring options variable. In JavaScript it means declaration of a global variable (or module-wide variable in node.js).
I have done a lot of reading around this, but ultimately the tutorials and guides I have found differ too much for me to get a decent grasp on this concept.
This is what I want to achieve:
1) Simple http request from our server [Any API for demonstration]
2) Run a function with data from (1). [Remove a property from the object]
3) Use result and length of (2) to run a loop of $http requests to our server. [Or any server]
4) This will result in 6 different objects. Run a function on these 6 objects. [Add a property]
5) Once ALL of this is done, run a separate function [Log "finished"]
How can this be achieved using promises? How do I pass data from (1) via a promise to (2)? Is this the right way to achieve what I need to do?
If anyone can show me how this should be structured it would be immensely helpful; I have kept the functions as simple as possible for this question.
Yes, promises are very nice to structure solutions for this kind of problems.
Simplified solution (more or less pseudo-code):
$http(...)
.then(function(response) {
// do something with response, for example:
var list = reponse.data.list;
// return it so that you can use it in the next 'then'.
return list;
})
.then(function(list) {
var promises = [];
angular.forEach(list, function(item) {
// perform a request for each item
var promise = $http(...).then(function(itemResponse) {
itemResponse.extraProperty = true;
return itemResponse;
});
// we make an array of promises
promises.push(promise);
});
// combine all promises into one and return it for the next then()
return $q.all(promises);
})
.then(function(itemsList) {
// itemsList is now an array of all parsed item responses.
console.log(itemsList);
});
(Hopefully this is right, I did not tested it.)
As you can see, you can return values in a callback to pass it to the next then(), or you can pass a promise, and this will result in calling the next callback when it resolves. $q.all() is used to combine multiple promises into one and resolve if all are resolved.
Edit: I realised that you can optionally leave out these three lines:
return list;
})
.then(function(list) {
But it is nice syntax though, because the separation of tasks is more visible.
Check code below, it could contains syntax error, the important is the structure. Step3 contains multiple(6) $http requests, it waits until the last request response to return a unique response object (array) containing response for each $http requets.
//Step 1
var Step1 = function () {
$http.get('api/controller').success(function (resp) {
var object1 = resp;
Step2(object1);
Step3(object1).then(function (resp) {
//resp.data is an array containing the response of each $http request
Step4(resp);
Step5();
});
});
}
//Step2
var Step2 = function(obj){
//do whatever with the object
}
//Step3
var Step3 = function (object1) {
var call = $q.defer();
var get1 = $http.get(object1[0].url);
var get2 = $http.get(object[1].url2);
//...
var get6 = $http.get(object[5].url6);
$q.all([get1, get2,..get6]).then(function (resp) {
call.resolve(resp);
});
return call.promise;
}
//Step4
var Step4 = function (resp) {
for (var i=0; i<resp.data.lenght;i++){
DoWhatEver(resp.data[i]);
};
}
//Step5
var Step5 = function () {
alert("Finished");
}
Step1(); //Call Step1 function
Don't know why you have difficulty implementing this, but maybe $q.all() is what you're missing:
var config1={method:'GET',url:'/api/...'};
$http(config1).success(function(resultsFrom1){
functionForResultsOf1(resultsFrom1);
})
var functionForResultsOf1 = function(resultsOf1){
//remove something from the result, assuming this is a synchronous operation
resultsOf1.splice()...;
var promises=makePromises(*pass whatever you want*);
$q.all(promises).then(function(aggregateOfAllCallsToServer){
angular.forEach(aggregateOfAllCallsToServer,function(data){
//do something to data from each call to the server
})
console.log("finished");
})
}
var makePromises = function(serverUrls){
var promises = [];
angular.forEach(serverUrls, function(url) {
var promise=$http({
url : '/api/'+url,
method: 'GET',
})
promises.push(promise);
});
return $q.all(promises);
}