How can I determine all ajax requests are resolved? - javascript

Here is my code:
function ajaxRequest(value, path, website){
var status = false;
return new Promise(function (resolve, reject) {
window[website] = $.ajax({
url : path,
type : 'GET',
data: { "name": value,
"_token": $('meta[name="_token"]').attr('content')
},
beforeSend: function(){
if(window[website] != null) {
window[website].abort();
}
},
success: function (people) {
status = true;
resolve([status, people]);
},
error: function (jqXHR, textStatus, errorThrown) {
reject([status, textStatus]);
},
timeout: 20000
});
});
}
And I call the function like this:
ajaxRequest('Jack', 'search/twitter', 'twitter').then(function(res) { console.log(res)}, function(err){console.log(err)});
ajaxRequest('Jack', 'search/instagram', 'instagram').then(function(res) { console.log(res)}, function(err){console.log(err)});
Now I need to know those two ajax requests are done. How can I do that?
Noted that I think I have to use promise.all(), but not sure how can I use it in my case.

You are right, promise.all() is invented to solve exactly that problem.
All it does is to return a new Promise that will resolved when all the given Promises are resolved.
In your case, you can wrap your 2 ajax calls with Promise.all something like that:
promise.all([
ajaxRequest('Jack', 'search/twitter', 'twitter').then(function(res) { console.log(res)}, function(err){console.log(err)}),
ajaxRequest('Jack', 'search/instagram', 'instagram').then(function(res) { console.log(res)}, function(err){console.log(err)})
]).then(([response1, response2]) => {
// Here you execute your logic when both of the promises are resolved.
})

You can pass function calls to $.when(). Note, jQuery.ajax() returns a jQuery promise object, using Promise constructor is not necessary
$.when(ajaxRequest(), ajaxRequest())
.then(function(...results) {
// do stuff with `results` array
})
.fail(function(jqxhr, textStatus, errorThrown) {
console.error(errorThrown)
})

Related

Sequentially chaining promises from 2 separate functions

I've reviewed all the related topics, but for some reason am unable to understand the correct syntax of resolving my case.
It's Javascript on Framework7 platform.
There is a function that has a $.each inside, which cycles through an array of items and executes an async POST operation to update that item.
There's a requirement to update items in two separate locations, so I execute this function twice, each time with a different arguments.
What would be the best approach to chain those two functions together in a sequence to be able to reload the page, as all async requests have been executed?
Thank you in advance!
UPDATE!
I managed to get this far, but promises.then doesn't result into "Success":
var tasks = [1,2,3];
var docs = [3,4,5];
var functions = [asyncOperation(tasks),asyncOperation(docs)]
var asyncOperation = function(items) {
return new Promise(function (resolve, reject) {
var deferreds = [];
items.forEach(function(i,e){
deferreds.push(
app.request({
url: buildurlnew,
type: "POST",
headers: buildheader,
contentType: "application/json;odata=verbose",
data: JSON.stringify(UpdatePayload),
success: function (data) {},
error: function(xhr, textStatus, errorThrown ) {}
)
});
return deferreds;
});
}
var promises = Promise.all(functions);
promises.then(function(results) {
//console.log("Success");
});
UPDATE 2 - The changed code, as per suggestions
var processtasks = function(array, dig) {
var getlistname = GetItemTypeForListName("Alerts")
var itemProperties = {'Title' :"Test"};
var UpdatePayload = {'__metadata': {'type': getlistname}};
for(var prop in itemProperties){
UpdatePayload[prop] = itemProperties[prop]
}
var buildurl = "<REST URL to Sharepoint list>"
var buildheader = { "Accept": "application/json;odata=verbose", "X-RequestDigest" : dig, "X-HTTP-Method": "MERGE", "If-Match": "*"}
return Promise.all(array.map(function(item) {
buildurlnew = buildurl+"("+item+")";
return app.request({
url: buildurlnew,
type: "POST",
headers: buildheader,
contentType: "application/json;odata=verbose",
data: JSON.stringify(UpdatePayload),
success: function (data) {},
error: function(xhr, textStatus, errorThrown) {}
});
}));
}
var processitems = function(listName, array, dig, type, source, web) {
var getlistname = GetItemTypeForListName(listName)
var buildurl = "<REST URL to Sharepoint list>"
var buildheader = { "Accept": "application/json;odata=verbose", "X-RequestDigest" : dig, "If-Match": "*"}
return Promise.all(array.map(function(item) {
var itemProperties = {'UserId' : app.data.UserID, 'Title' : item};
var UpdatePayload = {'__metadata': {'type': getlistname}};
for(var prop in itemProperties){
UpdatePayload[prop] = itemProperties[prop]
}
return app.request({
url: buildurl,
type: "POST",
headers: buildheader,
contentType: "application/json;odata=verbose",
data: JSON.stringify(UpdatePayload),
success: function (data) {},
error: function(xhr, textStatus, errorThrown ) {}
});
}));
}
processitems(listName, array, dig, type, source, web).then(function(r1) {
return processtasks(ids,dig);
}).then(function(r2) {
console.log(r2);
}).catch(function(err) {
console.log(err);
});
First you have to fix your asyncOperation() function because it is not properly returning a promise that is connected to the underlying async operations you're doing.
This code doesn't really make much sense. You have a basic structure of this:
var asyncOperation = function(items) {
return new Promise(function(resolve, reject) {
// body of function here
});
}
So far so good. But, what you need to do inside that promise executor function is start some async operation and call resolve or reject when you're done. You aren't doing that. Therefore, your promise never resolves or rejects.
Instead, you are returning an array of deferreds from the promise executor callback which does nothing. The promise executor function does not expect any return value so returning a value from it does nothing. You have to indicate completion of your asynchronous operation in the promise executor by calling either resolve(...) or reject(...).
If app.request() returns a promise, then you don't even need to make your own promise at all. You can just do something like this:
var asyncOperation = function(items) {
return Promise.all(items.map(function(item) {
return app.request(...);
}));
}
asyncOperation(...).then(function(results) {
// all done here
}).catch(function(err) {
// error here
});
items.map() generates an array of promises and Promise.all() returns a new single promise that monitors that array of promise and will resolve when all the promises in the array resolve or reject when any one of them rejects.
If app.request() does not return a promise, then you probably should make a "promisified" version that does so you can use it with promise functions like Promise.all(), perhaps using util.promisify().
To run two of these in parallel (which seems practical since they don't appear to depend upon one another), you could do this:
Then, once you are properly returning a promise for your function, if you have two of these, you can just use Promise.all() on more than one function call:
Promise.all([asyncOperation(...), asyncOperation(...)]).then(function(results) {
// both done here
// not the result may be an array of arrays
}).catch(function(err) {
// error here
});
What would be the best approach to chain those two functions together in a sequence
To run them in sequence, you can do this:
asyncOperation(args1).then(function(r1) {
return asyncOperation(args2);
}).then(function(r2) {
console.log(r2);
}).catch(function(err) {
console.log(err);
});

Where to write Success and Error

Here I'm using Angularjs1.x and here is my condition. If condition is success then show the table otherwise throw an error. I know some code if its Success.
AngCtrl.Js
$scope.BtnCall = function () {
ServiceCntrl.CallData().then(function (d) {
$scope.EmpData = d.data;
});
}
AngService.Js
eApp.service("ServiceCntrl", function ($http) {
var xx = '';
xx= $http({
data: formData,
method: 'post',
url: Url,
datatype: "json"
}).success(function (rsp) {
RspData = rsp;
return RspData;
}).error(function (rsp) {
console.log('Error');
});
return xx;
};
Your x.then receives two functions x.then(function(){}, function(){}); first function is called when promise is successfully resolved and second function is called if promise is rejected(failed).
If your service function is return $http promise then your first function can have a parameter named(anything you like) and it will have response data that you can use. Second function can receive error parameters if any.
you should look at angular $http service documentation.
If your service is returning the promise of the get request, then you can write
$scope.BtnCall = function () {
var x = ServiceCntrl.CallData();
x.then(function(response) {
//Success callback
//code on success goes here
//response.data
}, function(response) {
//error callback
//code on error goes here
// server returns response with an error status.
});
you can use the ng-show/ng-hide to show and hide the contents on the html page.
You can write your success/fail code as the following:
$scope.BtnCall = function() {
var x = ServiceCntrl.CallData();
x.then(function(result) {
// Success code here
// Do something and resolved the promise
}, function(reason) {
// Error code here
// You may handle/reject the reason in params
});
});
See also the Angular docs for $q.
The AngularJS $http service makes a request to the server, and returns a response
The example above executes the $http service with an object as an argument. The object is specifying the HTTP method, the url, what to do on success, and what to do on failure.
$scope.BtnCall = function () {
ServiceCntrl.CallData().then(function (d) {
$scope.EmpData = d.data;
});
}
AngService.Js :
eApp.service("ServiceCntrl", function ($http) {
var xx = '';
xx= $http({
data: formData,
method: 'post',
url: Url,
datatype: "json"
}).success(function (rsp) {
RspData = rsp;
return RspData;
}).error(function (rsp) {
console.log('Error');
});
return xx;
};

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)

Passing Promise as parameter doesn't trigger then or fail callbacks

Even though i am able to pass promise as parameter into error function, it does not trigger then & error function on the caller.?
if i change the code to put the AJAXError function inline, everything works fine.
Is it possible to pass around the promise around as parameter and still be able to trigger back then and fail on the caller up in the chain.
NOTE : promise is returned from palletizerService.addSerialNumberToPallet but but resolved/rejected in a different function AjaxFailed.
palletizerService.addSerialNumberToPallet ('p1','s1')
.then( function (response){
console.log('success');
},
function(error){
console.error(error);
});
Hereunder is the service that i am invoking from the above code
palletizerService.addSerialNumberToPallet = function (palletID, serialNumber) {
var deferred = $q.defer();
$.ajax({
url: url,
type: "GET",
dataType: "json",
methodName: methodName,
deferred: deferred.promise,
data: "palletID=" + palletID + "&serialNumber=" + serialNumber,
timeout: 20000, // 20 seconds for getting result, otherwise error.
error: AjaxFailed,
complete: function (jqXHR, textStatus) {
console.log("Ajax-finally : " + methodName);
},
beforeSend: function (qXHR, settings) {
},
success: function (data, textStatus, jqXHR) {
console.log("Ajax-Sucess " + methodName);
deferred.resolve(data);
}
});
return deferred.promise;
};
This is the external function
function AjaxFailed(qXHR, textStatus, errorThrown) {
try {
console.error('Ajax-Error in ' + methodName, qXHR.status, qXHR.responseText);
} catch (e) {
console.log("LocalMethod-Error in " + methodName);
console.error(e);
}
finally {
this.deferred.reject(qXHR);
}
}
Your problem is that you are passing the promise as the .deferred property of your options object. You cannot resolve/reject promises, they don't have a .reject method (and your code should throw when trying to call it). You would instead need to pass the deferred object you are holding.
However, this is a bad idea anyway. As jQuery.ajax does already return a promise, you should simply assimilate it using $q.when:
function (palletID, serialNumber) {
return $q.when($.ajax({
…
}));
}
(you will be able to omit all success and errror handlers, angular-promise does take care of them; if you want to add transformations or logging do those on the promise).
Yet that's still not optimal, in fact there hardly is a reason to use jQuery.ajax at all. Just use Angular's $http service, which also returns an angular promise right away.

Exploring / Browsing the promise chain

Given a promise object is it possible to browse through the different functions attached to it?
So having this situation (using angular):
var promise = $http({ method: 'GET', url: '/random' });
promise.then(function () { console.log('Value1'); });
promise.then(function () { console.log('Value2'); });
promise.then(function () { console.log('Value4'); });
promise.then(function () { console.log('Value5'); });
Is there a way to access to:
Get access to the n function
Add a function in between the current chain
var promise = $http({ method: 'GET', url: '/random' });
promise.then(function () { console.log('Value1'); });
promise.then(function () { console.log('Value2'); });
// New insertion:
promise.then(function () { console.log('Value3'); });
promise.then(function () { console.log('Value4'); });
promise.then(function () { console.log('Value5'); });
Get access to the nth function
No. The only purpose of a promise object is to execute the then callbacks when it is resolved. It's not supposed to let you manage the callbacks.
Of course, there may always be an implementation-dependent way to do that.
Add a function in between the current chain
No. The Promises/A+ spec mandates that "all respective […] callbacks must execute in the order of their originating calls to then." Therefore, you cannot insert something dynamically.
If you need such functionality, I'd recommend to use an array of things to do that you manage yourself. Then add a single callback to your promise that carries these tasks out in the order that you want.
isn't it possible to put it in your function with an argument ?
Exemple
var promise = $http({ method: 'GET', url: '/random' })
.success(function(data, status, headers, config) {
promise.resolve('value2');
})
promise.then(function (value) {
if(value == 'value1') {
console.log('Value1');
}
if(value == 'value2') {
console.log('Value2');
}
});

Categories