javascript promise return recursively to index - javascript

Ok, so I've got a loop like so:
underscore.each(dom.paramArray, function(value, i) {
fetchDataFromServerWithParams(value, i);
});
In my current example it loops 3 times, idealy like: 0,1,2
However, when I log the index of the function called, it logs: 1,0,2, why ?
And how can I get it to call the function recursively, so first it will process the function with index:0, then index:1, and lastly, index:2
I think it has something to do with the functions I am calling (buildResult and buildSubResult), but Im really not sure?
The function that is called by the loop looks like:
function fetchDataFromServerWithParams(param, index) {
//Create promise
let getData = $.ajax({
headers: {
Accept : "text/plain; charset=utf-8",
"Content-Type": "text/plain; charset=utf-8"
},
type: "GET",
url: configuration.apiEndpoint,
data: { id: param},
dataType: "json"
});
//When done processing promise, build result
getData.then(function (data) {
let generatedData;
console.log(index);
if(index === 0) {
generatedData = buildResult(data);
} else {
$.each($("ul.material a"), function( index, value ) {
var target = $(this).parent();
if($(this).data("id") == param) { //Refactor later to ===
target.parent().addClass("open-folder");
target.parent().parent().find("ul").addClass("open-folder");
generatedData = buildSubResult(data, target);
}
});
}
}), function(xhr, status, error) {
// Handle errors for any of the actions
handleError(error);
};
}

you can use async library or any other library for this purpose
also you can change the line
getData.then(function (data) {
to
return getData.then(function (data) {
and use this code instead of your underscore loop
(function sequenceCall(index){
fetchDataFromServerWithParams(dom.paramArray[index], index).then(function(){
sequenceCall(index+1);
});
})(0);

Related

How can I serve a "resolve" in my recursive AJAX calls so that I can use 'done'?

I am doing a few recurring AJAX calls where I pass an array from the front-end to the back-end and whenever it comes back to the front-end, the array gets smaller (by 1) and ultimately it'll be empty, therefore my recursive calls will stop.
Here's my calls:
function download_required_files(demo_data) {
var ajaxsecurity = setup_page_params.ajax_nonce;
jQuery.ajax({
url: ajaxurl,
type: 'POST',
dataType: 'json',
data: {
action: 'download_import_files_request',
security: ajaxsecurity,
content_install_request_data: JSON.stringify(demo_data),
},
success: function (response) {
console.log(response);
var data = response.data || false;
/**
* If no steps are left, meaning that all required files have been downloaded, proceed with the whole install process.
*/
if(!data.remaining_steps || !data.remaining_steps.length) {
return false;
}
if(data.can_continue !== 'yes') {
return false;
}
if(data.remaining_steps && data.remaining_steps.length) {
demo_data.steps_to_take = data.remaining_steps;
download_required_files(demo_data);
}
$('.demo-loader-content').fadeOut();
},
error: function (response) {
$('.demo-loader-content').fadeOut();
}
});
}
Assuming I have 2 steps to download files for, this download_required_files will run twice, then it'll be done, but if I do:
var download_process = download_required_files(demo_data) //Runs 2 times
download_process.done(function() { //Do stuff here once that function ran 2 times });
It gives me the: Cannot read property 'done' of undefined error and for good reason. That download_process is not a promise object for it to have that property, it's just...empty.
Where should I intervene in my download_required_files so that it signals to outside code that "Hey, in a promise environment, I'm done!"?
Although the result of the call to $.ajax is a jqXHR object, which is promise-like, for what you describe I think I'd go with your own native Promise (or Deferred if you prefer) to represent the overall recursive process:
function download_required_files(demo_data) {
return new Promise(function(resolve, reject) {
function worker() {
var ajaxsecurity = setup_page_params.ajax_nonce;
jQuery.ajax({
url: ajaxurl,
type: 'POST',
dataType: 'json',
data: {
action: 'download_import_files_request',
security: ajaxsecurity,
content_install_request_data: JSON.stringify(demo_data),
},
success: function (response) {
console.log(response);
var data = response.data || false;
/**
* If no steps are left, meaning that all required files have been downloaded, proceed with the whole install process.
*/
if(!data.remaining_steps || !data.remaining_steps.length) {
// *** All done
$('.demo-loader-content').fadeOut();
resolve();
} else if(data.can_continue !== 'yes') {
// *** All done; but is this an error condition? If so
// use `reject` instead of `resolve` below.
$('.demo-loader-content').fadeOut();
resolve();
} else {
demo_data.steps_to_take = data.remaining_steps;
worker(); // This is the internal recursive call
}
},
error: function (response) {
$('.demo-loader-content').fadeOut();
}
});
}
worker();
});
}
Or using Deferred instead:
function download_required_files(demo_data) {
var d = $.Deferred();
function worker() {
var ajaxsecurity = setup_page_params.ajax_nonce;
jQuery.ajax({
url: ajaxurl,
type: 'POST',
dataType: 'json',
data: {
action: 'download_import_files_request',
security: ajaxsecurity,
content_install_request_data: JSON.stringify(demo_data),
},
success: function (response) {
console.log(response);
var data = response.data || false;
/**
* If no steps are left, meaning that all required files have been downloaded, proceed with the whole install process.
*/
if(!data.remaining_steps || !data.remaining_steps.length) {
// *** All done
$('.demo-loader-content').fadeOut();
d.resolve();
} else if(data.can_continue !== 'yes') {
// *** All done; but is this an error condition? If so
// use `d.reject` instead of `d.resolve` below.
$('.demo-loader-content').fadeOut();
d.resolve();
} else {
demo_data.steps_to_take = data.remaining_steps;
worker(); // This is the internal recursive call
}
},
error: function (response) {
$('.demo-loader-content').fadeOut();
}
});
}
worker();
return d.promise();
}
This would be my approach, separating the individual AJAX requests from the looping over the content, and that also from the DOM updates:
function download_one_file(demo_data) {
return jQuery.ajax({
url: ajaxurl,
type: 'POST',
dataType: 'json',
data: {
action: 'download_import_files_request',
security: setup_page_params.ajax_nonce,
content_install_request_data: JSON.stringify(demo_data),
}
});
}
function download_loop(demo_data) {
return download_one_file(demo_data).then(function(data) {
if (!data) {
return Promise.reject();
} else if (data.remaining_steps && data.remaining_steps.length) {
demo_data.steps_to_take = data.remaining_steps;
return download_loop(demo_data);
} else {
return Promise.resolve();
}
});
}
function download_required_files(demo_data) {
return download_loop(demo_data).finally(function() {
$('.demo-loader-content').fadeOut();
});
}

$q promise with foreach

I am writing an angular service to work with SharePoint data and I have run into a problem. I have a function in my service that updates and single item and returns an $http promise which works fine. The problem is I am trying to write a function now that utilizes the first function to loop and update multiple items. I want it to return a single promise once all items have been updated and it should reject if any of the items being updated failed. Here is the function:
this.UpdateListItems = function (webUrl, listName, itemsJson) {
if (numItems == -1) {
numItems = itemsJson.length;
c = 0;
f = 0;
}
var promises = [];
itemsJson.forEach(function (itemProps) {
var itemPromise = this.UpdateListItem(webUrl, listName, itemProps.Id, itemProps)
.then(function (response) {
c++;
if (c == numItems && f == 0) {
numItems = -1;
return itemsJson[listName];
}
}, function (error) {
c++; f++;
alert("ERROR!");//This gets called first alert below
if (c == numItems) {
numItems = -1;
return $q.reject(error);
}
});
promises.push(itemPromise.$promise)
}, this);
return $q.all(promises)
.then(function (data) {
alert("IN SUCCESS"); //This always gets called immediately after first item success instead of waiting for all items to finish
}, function (error) {
alert("IN ERROR"); //This never gets called
});
};
The $q.all is returning immediately after the first item returns successfully instead of waiting for the rest of the async item calls. Any help is much appreciated, I am new to all this. Thanks!
EDIT: Adding UpdateListItem code as requested:
this.UpdateListItem = function (webUrl, listName, itemId, itemProperties) {
if (typeof lists[listName] === 'undefined') {
lists[listName] = [];
}
var post = angular.copy(itemProperties);
DataUtilitySvc.ConvertDatesJson(post);
return this.GetListItemById(webUrl, listName, itemId)
.then(function (item) {
return $http({
url: item.__metadata.uri,
method: 'POST',
contentType: 'application/json',
processData: false,
headers: {
"Accept": "application/json;odata=verbose",
"X-HTTP-Method": "MERGE",
"If-Match": item.__metadata.etag
},
data: JSON.stringify(post),
dataType: "json",
}).then(function (response) {
var temp = [];
temp.push(itemProperties);
DataUtilitySvc.MergeByProperty(lists[listName], temp, 'Id');
return response;
}, function (error) {
return $q.reject(error);
});
}, function (error) {
return $q.reject(error);
});
};
Seems like this.UpdateListItem function already returned promise by having $promise object. That's why you were able to have .then(chain promise) function over it.
So basically you just need to push returned itemPromise object instead of having itemPromise.$promise inside promises array. Basically when you are doing $promise, it creates an array of [undefined, undefined, ...] and will resolve as soon as for loop completed.
Change to
promises.push(itemPromise)
from
promises.push(itemPromise.$promise)
Somewhat this question can relate to this answer

Working with two URLs in ajax

I have to get values from two different URLs and then to merge it. I know it would much better if i'll get all of the data in one URL, but that's how i've got and i need to work with it.
I want to print out the value of a_value, but it's been printed out while b hasn't returned his value. I've read some articles of how to make the functions synchronous but still don't know how to implement it into my code, and don't know what is the best solution for my case. I'm pretty new with JavaScript and still need some help and guiding.
function any_function() {
$.ajax(
{
url : '/url1',
type: "GET",
success:function(data, textStatus, jqXHR)
{
$("#print").html(a(data));
}
});
}
function a(data){
x = 'any value' //`do something with data and insert to this variable`
a_value = x + b(`some id that extracted from data`)
return a_value
}
function b(id){
$.ajax({
url: '/url2',
type: 'GET',
success: function (data, textStatus, jqXHR) {
b_value = c(data, id)
}
});
return b_value
}
function c(data, id){
//do something with `data` and return the value
return c_value
}
function f() {
var request1 = $.ajax({
url : '/url1',
type: 'GET'
});
var request2 = $.ajax({
url: '/url2',
type: 'GET'
});
$.when(request1, request2).done(function(result1, result2){
data1 = result1[0]
data2 = result2[0]
// r1 and r2 are arrays [ data, statusText, jqXHR ]
// Do stuff here with data1 and data2
// If you want to return use a callback or a promise
})
}
This can be done in a synchronous-looking fashion with promises:
$.get(url1)
.then(function(data1){
return $.get(url2)
})
.then(function(data2){
return $.get(url3);
})
.then(function(data3){
// All done
});
You just need to make the second call in the success handler of the first one:
function any_function() {
$.ajax({
url : '/url1',
type: "GET",
success:function(data, textStatus, jqXHR) {
$("#print").html(a(data));
b("someId");
}
});
}
function a(data){
x = 'any value' //`do something with data and insert to this variable`
a_value = x + b(`some id that extracted from data`)
return a_value;
}
function b(id){
$.ajax({
url: '/url2',
type: 'GET',
success: function (data, textStatus, jqXHR) {
b_value = c(data, id);
return b_value;
}
});
}
function c(data, id){
//do something with `data` and return the value
return c_value
}

How to combine multiple call to Ajax Data base from different JS files

I have some code on a file that makes Ajax calls. This file is being called as a function by multiple other files that creates a new instance each time.
This is the JS code that is being called:
define(["underscore", "homeop", "domReady!"],
function (_, homeop, domready) {
var timeout = 500;
return function (opUrl, opList, onCallback) {
// IRRELEVANT CODE
var getFetch = function (optionName) {
$.ajax({
url: optionsUrl,
data: { optionNames: [optionName] },
type: "POST",
dataType: "json",
async: false,
traditional: true,
success: function (data) {
_.each(data, function (optionData, optionName) {
if (homeop.globalCache[optionName] === null) {
homeop.globalCache[optionName] = optionData;
}
});
},
error: function (message) {
console.error(message.responseText);
}
});
};
self.getInfo = function (optionName) {
if (homeop.globalCache[optionName] === undefined) {
if (!_.contains(homeop.getOption(), optionName)) {
getFetch(optionName);
}
// MORE IRRELEVANT CODE GOES HERE
In other JS files, I call the get function; for example
var these = new getOptions(optionsUrl, optionsList, onLoadCallback);
var getOpt = these.get(OptionsUrl);
The problem is I am making multiple calls to the get information from the database causing multiple call to my JS file. Each new instance of the JS file will create a ajax call.
Is there a way to wait for all the calls to be done and then get data from the database? In other words how can I somehow combine all the call to my 'getOption.js'?
Thanks
Try this.. You can also implement queue in place of stack
var optionStack = [];
var isAvailable = true;
var getFetch = function (optionName) {
if(isAvailable){
isAvilable = false; // function not available now
}
else {
optionStack.push(optionName)
return;
}
$.ajax({
url: optionsUrl,
data: { optionNames: [optionName] },
type: "POST",
dataType: "json",
async: false,
traditional: true,
success: function (data) {
_.each(data, function (optionData, optionName) {
if (homeop.globalCache[optionName] === null) {
homeop.globalCache[optionName] = optionData;
}
});
},
error: function (message) {
console.error(message.responseText);
},
done: function (){
isAvailable = true;
if(optionStack.length > 0){
getFetch(optionStack.pop());
}
}
});
};

Multiple ajax calls when previous one completes

I have these ajax calls that need to get called when the previous one is success, meaning once the first ajax is OK, call the 2nd ajax, once the 2nd ajax is OK call the 3rd one, etc so on. I started with a few ajax calls so it was fine to chain them up like this below but now I have about 20 of them and it'd be a mess to chain them up like this.
$.ajax({
url: 'urlThatWorks1',
success: function (data) {
//call someMethod1 with data;
$.ajax({
url: 'urlThatWorks2',
success: function (data) {
//call method2 with data;
//another ajax call ... so on
}
}.... 19 level deep
So I need to make it bit easier to read and maintain so I'm thinking something like
var ajaxArray = [];
var function1 = $.ajax('urlThatWorks1', data I get back from the 'urlThatWorks1' call);
myArray.push(function1);
var function2 = $.ajax('urlThatWorks2', data I get back from the 'urlThatWorks2' call);
myArray.push(function2);
//etc 19 others
myArray.each(index, func){
//Something like $.when(myArray[index].call()).done(... now what?
}
Hope this makes sense, I'm looking for a way of ajax call array from which I can call an ajax call on whose success I call the next ajax in the array. Thanks.
Create a recursive function to be called in sequence as the ajax requests return data.
var urls = [ "url.1", "url.2", ... ];
var funcs = [];
function BeginAjaxCalls()
{
RecursiveAjaxCall(0, {});
}
function RecursiveAjaxCall(url_index)
{
if (url_index >= urls.length)
return;
$.ajax(
{
url: urls[url_index],
success: function(data)
{
funcs[url_index](data);
// or funcs[urls[url_index]](data);
RecursiveAjaxCall(url_index + 1);
}
});
}
funcs[0] = function(data)
// or funcs["url.1"] = function(data)
{
// Do something with data
}
funcs[1] = function(data)
// or funcs["url.2"] = function(data)
{
// Do something else with data
}
Try
$(function () {
// requests settings , `url` , `data` (if any)
var _requests = [{
"url": "/echo/json/",
"data": JSON.stringify([1])
}, {
"url": "/echo/json/",
"data": JSON.stringify([2])
}, {
"url": "/echo/json/",
"data": JSON.stringify([3])
}];
// collect responses
var responses = [];
// requests object ,
// `deferred` object , `queue` object
var requests = new $.Deferred() || $(requests);
// do stuff when all requests "done" , completed
requests.done(function (data) {
console.log(data);
alert(data.length + " requests completed");
$.each(data, function (k, v) {
$("#results").append(v + "\n")
})
});
// make request
var request = function (url, data) {
return $.post(url, {
json: data
}, "json")
};
// handle responses
var response = function (data, textStatus, jqxhr) {
// if request `textStatus` === `success` ,
// do stuff
if (textStatus === "success") {
// do stuff
// at each completed request , response
console.log(data, textStatus);
responses.push([textStatus, data, $.now()]);
// if more requests in queue , dequeue requests
if ($.queue(requests, "ajax").length) {
$.dequeue(requests, "ajax")
} else {
// if no requests in queue , resolve responses array
requests.resolve(responses)
}
};
};
// create queue of request functions
$.each(_requests, function (k, v) {
$.queue(requests, "ajax", function () {
return request(v.url, v.data)
.then(response /* , error */ )
})
})
$.dequeue(requests, "ajax")
});
jsfiddle http://jsfiddle.net/guest271314/6knraLyn/
See jQuery.queue() , jQuery.dequeue()
How about using the Deferred approach. Something like:
var arrayOfAjaxCalls = [ { url: 'https://api.github.com/', success: function() { $("#results").append("<p>1 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>2 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>3 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>4 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>5 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>6 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>7 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>8 done</p>"); } },
{ url: 'https://api.github.com/', success: function() { $("#results").append("<p>9 done</p>"); } }
];
loopThrough = $.Deferred().resolve();
$.each(arrayOfAjaxCalls, function(i, ajaxParameters) {
loopThrough = loopThrough.then(function() {
return $.ajax(ajaxParameters);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="results"></div>
You could use the async library, which has a bunch of functions like waterfall or series which could solve your problem.
https://github.com/caolan/async#series
https://github.com/caolan/async#waterfall

Categories