Promise being resolved immediately - javascript

I need to retrieve data from multiple API calls and aggregate them with the click of a button in the UI. I need to print the data once they all have been executed fully. I am returning a Promise from the function that runs a for-loop to make all API calls in succession. I am also processing the API call results as I am receiving them. Hence I resolve the promise just outside that for-loop. (Reminder, the for-loop makes some API calls inside.) Now, when I call this function, the promise gets resolved immediately and the success function runs which basically gives me empty aggregate data which is not expected. Where/how should I resolve the promise in such case?
Basic structure of my code:
forLoopFunction(xyz)
.then(function(){ // print aggregate data when successfull})
.catch(function(){ // print couldn't retrieve data })
forLoopFunction(){
return new Promise(resolve, reject){
for(var i=0; i<...){
api1_Call().$promise
.then(
api2_call().$promise
.then(
//work on aggregate data
)
.catch(reject(error))
).
.catch(reject(error))
}
//end of for loop
resolve(aggregated_data);
}
}
Edited code structure:
//$scope.requests is populated before this function call, have seen it printed
$scope.myFunc()
.then(function(result){console.log(result)})
.catch(function(error){console.log("failed")})
$scope.myFunc = function() {
var promiseArray = [];
for(var i=0; i<$scope.requests.data.length; i++) {
promiseArray.push(new Promise(function(resolve, reject) {
var requests= $scope.requests.data[i];
WorkflowsService.get({id: requests.key}).$promise
.then(
function (data) {
StepsService.query({workflowId: workflowData.id}).$promise
.then(
function (steps) {
//some variables calculation
var metadata = []; //this belongs to a single work request
//some more values pushed to metadata array
//switch case to calculate appropriate endpoint and system id
//$.ajaxSetup({async: false});
$.ajax({
url: apiEndpoint + systemId,
type: 'GET',
success: function(resp){
compartmentId = resp.compartmentId;
$.get("/api/compartments/" + compartmentId, function (resp) {
//some values pushed to metadata
});
},
error: function(resp) {
//put dummy data to metadata array
}
});
//construct a URL to be pushed into metadata
$scope.metadataString += metadata.join("\n");
Promise.resolve("resolved promise");
})
.catch( function(error){ Promise.reject("rejected"); console.log(error) } )
})
.catch( function(error){ Promise.reject("rejected"); console.log(error) } )
});
promiseArray.push(promiseObject);
}
return Promise.all(promiseArray).then(function() { return $scope.metadataString; });
}

You should maintain promise for all api call then resolve all in one time which I am doing by using promise.all, this will give you data in then callback in the form of array of each api call.
forLoopFunction(xyz)
.then(function(data){ console.log(data); // print aggregate data when successfull})
.catch(function(){ // print couldn't retrieve data })
forLoopFunction(){
var promiseArray = [];
for(var i=0; i<...){
var promiseObject = new Promise(function(resolve, reject){
api1_Call().$promise
.then(
api2_call().$promise
.then(
resolve(aggregated_data);
)
.catch(reject(error))
).
.catch(reject(error))
});
promiseArray.push(promiseObject);
}
return Promise.all(promiseArray)
}

Related

foreach loop with promise execute API call AFTER the first one is done

let array = [1,2,3];
array.forEach(function(item,index) {
var data = fetch('https://reqres.in/api/products/'+item);
//wait for response after response come then next iterating a loop (first,second,third)
});
i want to run like
first call api
wait for response
show into html
but currently my script call all three times api without wait for response
i want to wait for response and after done then called next api ..like that
use for of instead of forEach loop and make promise for request like that
var items = [1, 2];
function makeRequest(index) {
return new Promise((resolve, reject) => {
$.ajax({
url: "https://reqres.in/api/products/"+index,
type: "GET",
success: function(response){
console.log(response.data);
resolve(response);
},
error: function (error) {
reject(error)
}
})
})
}
async function runAsync(locations){
for(let location of locations){
console.log('value ' + location);
await makeRequest(location);
};
}
runAsync(items);
async function asyncExample() {
let array = [1,2,3];
for (let i = 0; i < array.length; i++) {
var data = await fetch('https://reqres.in/api/products/'+item);
});
}
You have to use Async/Await to make the code wait for the API call. Async/await doesn't work properlly with loop's, to avoid problens with that you should use a normal for ou a for of. More examples: https://zellwk.com/blog/async-await-in-loops/
What you need is to syncronize $http calls. Javascript can do that via Promises.
var promise = new Promise(function(resolve, reject){ resolve("OK");});
let array = [1,2,3];
array.forEach(function(item,index) {
promise.then(
$http.get('https://reqres.in/api/products/'+item)
).then(function (response){
...
});
});
promise.catch(function(response) {
...
});

How to get data from async functions and after getting data allow the next code to execute

I get some data from mongodb using mongoose find() and perform some validation on that data, but the problem is that this function is async and it does not wait for the data to be completed and execute the next code.
and when the next code is executed it enables to perform because it has null data. then i wrote my validation logic in the async function so that when data is available only then it move to next code but on every return it sends undefined data.
function isValidObject(obj) {
schemaData.find({}, (error, data) => { // calls data from db
var contactSchema = data; // this is data i need
if(//validation fails){
return "wrong data";// every time it returns undrfined on every
// condition
}
});
}
var validationResp = isValidObject(obj);
console.log(validationResp); // output is undefined
i also used "await" to wait for the data, but in that case it return [object promise] on every return statement
use async/await
In your case:
async function isValidObject(obj) {
let data = await schemaData.find({}); // wait until it resolve promise
//do your validaion
return data;
}
isValidObject(obj).then((validationResp)=>{console.log(validationResp)});
use the then() method it return a promise
var promise1 = new Promise(function(resolve, reject) {
resolve('Success!');
});
promise1.then(function(value) {
console.log(value);
// expected output: "Success!"
});
more details at MDN Using the then method
Are you familiar with "promises".You can use ".then(callback)". It will wait until async function is executed. And you can perform your validation in the callback function
User.findById(id)
.then(function(user) {
if (!user) {
return "here you return an error";
}
return "you return OK with payload from database";
})
.catch(next);
What is happening in your case is that when you assign var validationResp = isValidObject(obj); the function isValidObject(obj) has not returned anything and has only scheduled a callback(This is a very important concept when working with callbacks). As pointed out above you need to use Promises. The below is an example of your case as to how you can use Promises.
function isValidObject(obj){
return new Promise((resolve,reject) => {
schemaData.find({}, (error, data) => { // calls data from db
if(validation fails){
reject(error)// every time it returns undrfined on every
// condition
}else{
var contactSchema = data; // this is data i need
resolve(data)// You can resolve the promise with the data directly instead
// of assigning it to a variable or you can use (resolve(contactSchema))
}
})
})
}
After this when you want to use the data you can use do something like the below code snippet.
isValidObject(obj)
.then(result => {
// Use the result object here. This is the result from the Database
})
.catch(error => {
//Handle the Error
})

Javascript - .length gives me 0 length

Array is initialized
var Collect = [];
So I have a promise arguments so that I'll have a asynchronous in execution since retrieving data from firebase which then push in the array Collect takes a bit of time. Here's my code:
function loadTables(){
var promise = getDataFirebase();
promise.then(function(){
console.log("firsst");
return ProcessOfData();
}).then(function(){
console.log(Collect); //when printed, it shows the elements collected from firebase so the array is not 0.
console.log(Collect.length); // but when printeed here. it gives me 0. why?
return EndProcessLog();
}).then(function(){
});
}
Codes when retrieving data from firebase:
function getDataFirebase(){
return new Promise (function(resolve,reject){
refReview.on("value", function(snap){
var data = snap.val();
for(var key in data){ //data retrieved must be REVIEWEE NAME, REWIEVER NAME, RATING, ,CONTENT
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ",data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ",data[key].reviewerID.lastname),
rating:data[key].rating,
content: data[key].content
})
}//end of for loop
}); //end of snap
resolve();
});
}
Why does it not work? Because you are resolving the promise before the asynchronous method runs. The reason why the object shows the value is the console lazy loading the object.
What do you do? Move the resolve line after the for loop inside the callback.
refReview.on("value", function(snap) {
var data = snap.val();
for (var key in data) { //data retrieved must be REVIEWEE NAME, REWIEVER NAME, RATING, ,CONTENT
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ", data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ", data[key].reviewerID.lastname),
rating: data[key].rating,
content: data[key].content
})
} //end of for loop
resolve(); < --RIGHT
}); //end of snap
// resolve(); <-- WRONG
Ideally with a promise you do not use global variables, you pass the value through the resolve.
var myPromise = new Promise(function(resolve, reject) {
var str = "Hello!";
resolve(str);
});
myPromise.then(function(value) {
console.log(value);
});

javascript api call, send and handle data in specific order

I have this API call where i make sure the data return in the same order i send it. However, i realized thats not really what i want, i want to make sure the data is send and taken care of one at a time.
data[n] has returned before data[n+1] is send.
the reason for this is:
If i do it as seen below, the server still gets it in a random order, and therefor saves the data in my DB in a random order. (or well not random, heavier data gets processed slower)
var promiseArray = [];
for (var i = 0; i < data.length; i++) {
var dataPromise = $http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then (function (response) {
//return data for chaining
return response.data;
});
promiseArray.push(dataPromise);
}
$q.all(promiseArray).then(function (dataArray) {
//succes
}).catch (function (errorResponse) {
//error
});
how can i make sure the data is send and processed and returned, one at a time in a smooth way ?
You could do something like this:
var i = -1;
processNextdata();
function processNextdata() {
i++;
if(angular.isUndefined(data[i]))
return;
$http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then(processNextdata)
}
Update:
Callback after every result:
var i = -1;
processNextdata();
function processNextdata() {
i++;
if(angular.isUndefined(data[i]))
return;
$http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then(function(result) {
// do something with a single result
return processNextdata();
}, errorCallback);
}
Callback after everything is done:
var i = -1, resultData = [];
processNextdata()
.then(function(result) {
console.log(result);
}, errorCallback);
function processNextdata() {
i++;
if(angular.isUndefined(data[i]))
return resultData;
$http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then(function(result) {
resultData.push(result.data);
return processNextdata();
}, $q.reject);
}
When using the Promise.all([...]) method, the documentation shows the following:
The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
What this tells us is that there is no expected order of synchronized operations, but in fact the promises run parallel to one another and can complete in any order.
In your case, there is an expected order that you want your promises to run in, so using Promise.all([...]) won't satisfy your requirements.
What you can do instead is execute individual promises, then if you have some that can run in parallel use the Promise.all([...]) method.
I would create a method that takes a request as an argument, then returns the generated promise:
function request (req) {
return new Promise(function (resolve, reject) {
request({
url: url
, port: <port>
, body: req
, json: <true/false>
, method: '<POST/GET>'
, headers: {
}
}, function (error, response, body) {
if (error) {
reject(error);
} else {
resolve(body);
}
});
});
You can then call this function and store the result:
var response = request(myRequest);
Alternatively, you could create an array of your requests and then call the function:
var requests = [request1, request2, ..., requestN];
var responses = [];
for (var i = 0; i < requests.length; i++) {
responses.push(request(requests[i]));
}

Process loop synchronously in angular

I have an $http request that is returning a bunch of rows. I need to process each of those results synchronously. Having trouble wrapping my brain around Angular.
Each of the records needs to be processed against a local SQLite database on an iOS device, and that is an asynchronous call.
If any of the loop records fail, I need to abort the entire operation (and loop).
Here's the code to see if it helps...
var username = $rootScope.currentUser;
window.logger.logIt("Executing incremental sync with username " + username);
var url = $rootScope.serviceBaseUrl + 'SyncData/GetSyncItems?userid=' + username + '&lastSyncDate=' + lastSyncDate.toString();
var encoded = encoder.encode($CONFIG.serviceAccount);
$http.defaults.headers.common.Authorization = 'Basic ' + encoded;
$http({ method: 'Get', url: url })
.success(function(data, status, headers, config) {
var processes = [];
for (var i in data) {
var params = data[i].Params;
var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
var paramsArray = paramsMassaged.split(",");
var process;
if (data[i].TableName === "Tabl1") {
window.logger.logIt("setting the process for a Table1 sync item");
process = $Table1_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
} else if (data[i].TableName === "Table2") {
window.logger.logIt("setting the process for an Table2 sync item");
process = $Table2_DBContext.ExecuteSyncItem(data[i].Query, paramsArray);
} else {
window.logger.logIt("This table is not included in the sync process. You have an outdated version of the application. Table: " + data[i].TableName);
}
window.logger.logIt("got to here...");
processes.push(process);
}
window.logger.logIt("Finished syncing all " + data.length + " records in the list...");
$q.all(processes)
.then(function (result) {
// Update the LastSyncDate here
$DBConfigurations_DBContext.UpdateLastSyncDate(data[i].CreatedDate);
alert("finished syncing all records");
}, function (result) {
alert("an error occurred.");
});
})
.error(function(data, status, headers, config) {
alert("An error occurred retrieving the items that need to be synced.");
});
Table2 ExecuteSyncItem function:
ExecuteSyncItem: function (script, params) {
//window.logger.logIt("In the Table2 ExecuteSyncItem function...");
//$DBService.ExecuteQuery(script, params, null);
var deferred = $q.defer();
var data = $DBService.ExecuteQuery(script, params, null);
if (data) {
deferred.resolve(data);
} else {
deferred.reject(data);
}
return deferred.promise;
}
DB Service code:
ExecuteQuery: function (query, params, success) {
$rootScope.db.transaction(function (tx) {
tx.executeSql(query, params, success, onError);
});
},
Update: In response to Maxim's question "did you log process method". Here's what I'm doing...
ExecuteSyncItem: function (script, params) {
window.logger.logIt("In the Experiment ExecuteSyncItem function...");
//$DBService.ExecuteQuery(script, params, null);
var deferred = $q.defer();
var data = $DBService.ExecuteQuery(script, params, function () { window.logger.logIt("successCallback"); });
if (data) {
window.logger.logIt("success");
deferred.resolve(data);
} else {
window.logger.logIt("fail");
deferred.reject(data);
}
return deferred.promise;
}
"data" is undefined everytime. "fail" is logged everytime, as well as "successCallback". Also, the executeQuery IS working, and updating the data the way I expect.
So now, it's just a matter of the promise syntax I guess. If the ExecuteQuery isn't actually populating the "data" variable since it's asynchronous, how do I set the deferred.resolve() and deferred.reject stuff?
You are on right way
I would use $q.all
$q.all([async1(), async2() .....])
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
For example:
var processes = [];
processes.push(Process1);
processes.push(Process2);
/* ... */
$q.all(processes)
.then(function(result)
{
/* here all above mentioned async calls finished */
$scope.response_1 = result[0];
$scope.response_2 = result[1];
}, function (result) {
alert("Error: No data returned");
});
From your example you run in loop and call async methods (Process1, Process2) 10 times (8 and 2 respectively). In order to use $q.all the Process1, Process2 must return promise.
So I would write it something like that:
var Process1 = function(stuff) {
var deferred = $q.defer();
var data = $DBService.ExecuteQuery(stuff.query); // This is asynchronous
if (data ) {
deferred.resolve(data);
} else {
deferred.reject(data);
}
return deferred.promise;
}

Categories