Trouble forming a promise chain - javascript

I have a function that has to connect to DB to get a upload token, then upload a file, then close the stream and log the file in DB. I'm having trouble chaining all this together.
var saveNewRequest = function (image_arr) {
return $.ajax({
url: 'http://' + AppVar.ServerUrlWithPort + '/restapi/MtReqNewRequest_SaveData',
type: 'POST',
data: JSON.stringify({
'SessionId': AppVar.SessionId,
'Name': $('#MtReqNewRequest_name').val(),
'Desc': $('#MtReqNewRequest_desc').val(),
'Obj': $('#MtReqNewRequest_obj').val(),
'Priority': $('#MtReqNewRequest_priority2').val(),
'Status': $('#MtReqNewRequest_status2').val(),
'Type': $('#MtReqNewRequest_type2').val()
}),
dataType: 'json',
contentType: "application/json",
timeout: 10000
}).done(function (response) {
if (response.ResultCode === '0') {
if (image_arr.length != 0) {
//this is recursively called upload function which returns jQuery promise (this works as intended)
// the promise resolves with RequestId which I need later on
return uploadImages(image_arr, image_arr.length, 0, response.RequestId)
} else {
//I would like this to return just this RequestId
Promise.resolve(response.RequestId)
}
} else {
Promise.reject().promise();
}
}).fail(function (x, t, m) {
if (t === "timeout") {
reject("Timeout: " + t);
} else {
reject($.i18n('Error-RetrivingDataProblem'));
}
})
}
And I call this in an event:
MtReq.saveNewRequest(image_arr).then(function (output) {
AppVar.nav.popPage().then(function () {
Utility.hideModalWithProgressBar();
if (!isNaN(output)) {
setTimeout(500, AppVar.nav.pushPage("MtReqRequestPage.html", { animation: "slide", id: output }));
}
})
}).catch(function (e) {
Utility.hideModalWithProgressBar();
ons.notification.alert(e);
})
I need to pass the RequestID to the AppVar.nav.pushPage, to open the page I just created. However, I'm getting whole response of the very first Ajax request in saveNewRequest.
This is Cordova app, using OnsenUI framework (but that's not relevant to the problem). Also, I'm using latest BluebirdJs as Promise polyfill (which to my knowledge should make JS and jQuery promises compatible).
Thanks for any help!

Substitute .then() for .done(); .done() returns same jQuery promise object returned by $.ajax(). return the Promise or other value from .then().
var saveNewRequest = function (image_arr) {
return $.ajax({
url: 'http://' + AppVar.ServerUrlWithPort + '/restapi/MtReqNewRequest_SaveData',
type: 'POST',
data: JSON.stringify({
'SessionId': AppVar.SessionId,
'Name': $('#MtReqNewRequest_name').val(),
'Desc': $('#MtReqNewRequest_desc').val(),
'Obj': $('#MtReqNewRequest_obj').val(),
'Priority': $('#MtReqNewRequest_priority2').val(),
'Status': $('#MtReqNewRequest_status2').val(),
'Type': $('#MtReqNewRequest_type2').val()
}),
dataType: 'json',
contentType: "application/json",
timeout: 10000
}).then(function (response) {
if (response.ResultCode === '0') {
if (image_arr.length != 0) {
//this is recursively called upload function which returns jQuery promise (this works as intended)
// the promise resolves with RequestId which I need later on
return uploadImages(image_arr, image_arr.length, 0, response.RequestId)
} else {
//I would like this to return just this RequestId
// added `return`
return Promise.resolve(response.RequestId)
}
} else {
// note `return`, removed `.promise()`
return Promise.reject()
}
}).fail(function (x, t, m) {
if (t === "timeout") {
// included `Promise`, chain `.reject()`
// note, `return`
return Promise.reject("Timeout: " + t);
} else {
// note `Promise.reject()`, added `return`
return Promise.reject($.i18n('Error-RetrivingDataProblem'));
}
})
}

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();
});
}

Returning the correct promise in angular

I'm having trouble returning the correct promise for a service in angular.
this is my function:
postToSP.post($scope.sharePointURL, data).then(function() {
$scope.gettingData = false;
$scope.yammerListName = "Successfully posted to SP";
}).catch(function(e){
//console.log("Error: ", e);
$scope.yammerListName = "Sorry we couldn't post to that page, please make sure your column names are EXACTLY the same!"
$scope.gettingData = false;
throw e;
});
And this is my service, i get the error: "Unable to get property 'then' of undefined or null reference". I know it's because i'm not returning the promise properly but I can't figure out how to do it correctly. Please help, thanks in advance.
app.service("postToSP", function($http) {
//Submit to SP function
this.post = function(originalurl,data){
console.log(data);
var url = originalurl.split("Lists/")[0];
var listname = originalurl.split("Lists/")[1].split("/")[0];
//if the row is checked send it, if not jump to the next row
//run the function, continue until the end and break
var i = 0;
return letsPost(i);
function letsPost (i) { //i<data.length; i++
if (data[i].checked == false) {
i++;
return letsPost(i);
} else {
var formattedText = document.getElementById("text"+i).innerHTML.toString() ;
var formattedCreated = document.getElementById("created"+i).innerHTML.toString();
var formattedLikes = document.getElementById("likes"+i).innerHTML.toString();
var formattedLinks = document.getElementById("links"+i).innerHTML.toString();
var uploadData = { //change this for input data
'__metadata': { 'type': 'SP.Data.' + listname + 'ListItem' },
'Title': i + "",
'Likes': formattedLikes,
'Post_x0020_Date': formattedCreated,
'Post_x0020_Links' : formattedLinks,
'Post_x0020_Text': formattedText
};
console.log(uploadData);
createListItem(url, listname, uploadData)
.done(function (columnData) {
console.log('Added row' + i);
// if there is more data
if (i < data.length) {
i++;
return letsPost(i);
//add new data and continue the function
} else {
return;
}
})
.fail(function (error) {
console.log(JSON.stringify(error));
alert("Error:" + JSON.stringify(error));
throw error;
});
//Function to get form digest token
function getFormDigest(webUrl) {
return $.ajax({
url: webUrl + "/_api/contextinfo",
method: "POST",
headers: { "Accept": "application/json; odata=verbose" }
});
};
//Function to create the list item
function createListItem(webUrl, listName, itemProperties) {
$.ajax({
url: url + "/_api/web/lists/getbytitle('" + listName + "')/items",
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
console.log(data.d.results);
},
error: function (data) {
console.log(data);
}
});
return getFormDigest(webUrl).then(function (data) {
return $.ajax({
url: webUrl + "/_api/web/lists/getbytitle('" + listName + "')/items",
type: "POST",
processData: false,
contentType: "application/json;odata=verbose",
data: JSON.stringify(itemProperties),
headers: {
"Accept": "application/json;odata=verbose",
"X-RequestDigest": data.d.GetContextWebInformation.FormDigestValue
}
});
});
};
};
};
};
});
in your function declare the promise first
this.post = function(originalurl,data){
var deferred = $q.defer();
the data that you want to return use
deferred.resolve(dataToReturn)
and at the end of your function add
return deferred.promise;
From what I understand your code, mistake you are doing is you are returning the promise returned from getFormDigest but also applying then function on it and returning another promise. If you dont return getFormDigest nothing will be returned since its async.
To solve it you can use angular $q library and return and independent promise. Resolve that promise in your then function where you are returning a promise and no need to return getFormDigest so only one promise will be returned and hopefully your problem will be resolved.
In simple way you can achieve it..i hope it make sense
//in your controller
yourService.addData(yourPayload);
.then(function (cou) {
$scope.data = cou.data;
});
//in your service
this.addData = function (data) {
var response = $http({
method: "POST",
url: 'your url',
data: data,
dataType: "json"
});
return response;
}

Nested promise functions

I have a button that I want to track when the user press it, and if the tracking ajax call returns data, then execute a promise, when thats all done, continue with the button event.
Below is what I have so far, however the button event and the 2nd promise fire at the same time. The button event isn't waiting for the 2nd promise to resolve
Click button
AJAX call records the event
If AJAX doesn't return a question then alert hello.
If the AJAX call returns a question, then build and open modal, when modal closes, then alert hello.
$('.my_button').on('click', function() {
ui_tracking('button_1').then(function () {
alert('Hello');
});
});
function ui_tracking(type, payload) {
var deferred = $.Deferred();
var log_action = function () {
$.ajax({
url: '/api/submit_action',
type: 'POST',
dataType: 'json',
data: {
type: type,
payload: JSON.stringify(payload)
},
success: function(results, textStatus, xhr) {
if (typeof(results) !== 'undefined' && results !== null) {
if (typeof(results.data) !== 'undefined' && results.data !== null) {
if (results.data.question) {
startQuestion(results.data.question).then(function () {
deferred.resolve();
}, function() {
deferred.reject();
});
}
}
}
deferred.resolve();
},
error: function (xhr, textStatus, errorThrown) {
deferred.reject();
}
});
};
log_action();
return deferred.promise();
}
function startQuestion(question_data) {
var deferred = $.Deferred();
var openQuestion = function () {
$('#question-modal .modal-body .question').html(question_data.question).attr('data-question-id', question_data.id);
$('#question-modal').modal('show').on('hidden.bs.modal', function (e) {
deferred.resolve();
$('#question-modal').unbind('hidden.bs.modal');
});
};
openQuestion();
return deferred.promise();
}
sf
your success: callback calls deferred.resolve(); straight away (at the end, after the if condition)
success: function(results, textStatus, xhr) {
if (typeof(results) !== 'undefined' && results !== null) {
if (typeof(results.data) !== 'undefined' && results.data !== null) {
if (results.data.question) {
startQuestion(results.data.question).then(function () {
deferred.resolve();
}, function() {
deferred.reject();
});
}
}
}
// this gets called regardless of the above conditions!
deferred.resolve();
Knowing $.ajax returns a (jQuery) Promise, I believe you could simplify ui_tracking function as follows:
function ui_tracking(type, payload) {
return $.ajax({
url: '/api/submit_action',
type: 'POST',
dataType: 'json',
data: {
type: type,
payload: JSON.stringify(payload)
}
}).then(function(results) {
if (results && results.data && results.data.question) {
return startQuestion(results.data.question);
}
});
}
In the above .then, if the conditions are not met, the return is undefined ... basically the same thing as you were doing with deferred.resolve() - the returned Promise will be resolved to undefined once the ajax completes
However, if the conditions are all met, the return is the Promise returned by startQuestion - which will mean the returned Promise will be that which is returned by startQuestion - therefore your code will wait on that promise to resolve before continuing
Also, no need for error handling that simply returns a rejected promise - let the outer call handle errors
Alternatively, you could write the whole lot as
$('.my_button').on('click', function() {
ui_tracking('button_1')
.then(startQuestion)
.then(function () {
alert('Hello');
});
});
function ui_tracking(type, payload) {
return $.ajax({
url: '/api/submit_action',
type: 'POST',
dataType: 'json',
data: {
type: type,
payload: JSON.stringify(payload)
}
});
}
function startQuestion(results) {
var deferred;
var openQuestion = function (question_data) {
$('#question-modal .modal-body .question').html(question_data.question).attr('data-question-id', question_data.id);
$('#question-modal').modal('show').on('hidden.bs.modal', function (e) {
deferred.resolve();
$('#question-modal').unbind('hidden.bs.modal');
});
};
if (results && results.data && results.data.question) {
deferred = $.Deferred();
openQuestion(results.data.question);
return deferred.promise();
}
}
Sure the logic has moved about, so maybe not exactly what you'd like
You can encapsulate each "step" in a function returning a promise (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and then "chain" them.
This is a great post on executing promises in a series: http://www.datchley.name/promise-patterns-anti-patterns/#executingpromisesinseries (you may need a polyfill for your promising: https://github.com/stefanpenner/es6-promise)

javascript promise return recursively to index

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);

$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

Categories