Sequentially chaining promises from 2 separate functions - javascript

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

Related

How can I determine all ajax requests are resolved?

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

Calling closure functions after ajax has run

I'm new to the module pattern and closures in javascript. I'm trying to create a module that gets JSON data so I can display it on my webpage.
var EmailLogs = (function(){
var emailLogs = [];
var config = {};
function init(options){
for(var prop in options) {
if(options.hasOwnProperty(prop)){
config[prop] = options[prop];
}
}
setEmailLogs();
}
function setEmailLogs(){
$.ajax({
type: "POST",
headers : { "cache-control": "no-cache" },
url: "../../ajax/adminGetEmailLogs.php",
data: {options: JSON.stringify(config)},
dataType: 'json',
success: function(values){
if(values.success)
{
emailLogs = values.emailLogs;
}
}
});
}
function getEmailLogs(){
return emailLogs;
}
return{
init: init,
getEmailLogs: getEmailLogs,
}
})();
var options = {
sData : [
'email_id'
],
filters : {
user_id : 44860,
}
}
EmailLogs.init(options);
console.log(EmailLogs.getEmailLogs());
I'm trying to run the ajax call when init is run. Then I'm looking to get the emailLogs variable to display. I presume because my ajax is being run async, that's why I can't get my variables after. How do I make sure setEmailLogs() has ran before I running getEmailLogs().
$.ajax() implements the Promise interface and will return a Deferred object, see the jQuery docs
The jqXHR objects returned by $.ajax() as of jQuery 1.5 implement the Promise interface, giving them all the properties, methods, and behavior of a Promise (see Deferred object for more information).
This means that you can use the promise interface to make sure the desired method is run after $.ajax() is done.
Modify your code to something make use of the promise interface. Basically $.ajax() returns to you the promise that it will be executed. You can then chain your next method to execute once the promise is satisfied. You often see patterns like this chaining multiple promises (in psuedo code):
doAjaxRequest()
.then(doSomethingElse)
.then(doAnotherSomethingElse)
This way you can write quite clean async code as oppossed to haven a lot of callbacks invoking eachother.
The documentation for Q promises has some good examples that explain the way promises work.
I modified your snippet to uses promises:
var EmailLogs = (function(){
var emailLogs = [];
var config = {};
function init(options){
for(var prop in options) {
if(options.hasOwnProperty(prop)){
config[prop] = options[prop];
}
}
setEmailLogs().done(function(values) {
if(values.success)
{
console.log(values.emailLogs)
}
});
}
function setEmailLogs(){
return $.ajax({
type: "POST",
headers : { "cache-control": "no-cache" },
url: "../../ajax/adminGetEmailLogs.php",
data: {options: JSON.stringify(config)},
dataType: 'json'
});
}
return{
init: init,
getEmailLogs: getEmailLogs,
}
})();
var options = {
sData : [
'email_id'
],
filters : {
user_id : 44860,
}
}
EmailLogs.init(options);

$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

Making a callback in a chain?

Can we make a callback in a chain like this?
Widget.update(...).onUpdate(function(data){
console.log('updated');
});
current code,
var Gateway = {};
Gateway.put = function(url, data, callback) {
$.ajax({
type: "POST",
dataType: "xml",
url: url,
data: data,
async: true,
success: function (returndata,textStatus, jqXHR) {
callback(returndata);
}
});
};
var Plugin = function() {};
Plugin.prototype = {
update : function(options, callback) {
/// other stuff
Gateway.put($url, $data, function(data){
callback(data);
}
return this;
}
}
usage,
var Widget = new Plugin();
Widget.put({
button: options.button
}, function(data){
console.log('updated');
});
but ideally,
Widget.update(...).onUpdate(function(data){
console.log('updated');
});
EDIT:
at jsfiddle.
What you are trying to do will work however you need to pass your callback to update
Widget.update(yourOptions, function(data){
console.log('updated');
});
You could also return your ajax request directly and chain onto it
var Gateway = {};
Gateway.put = function(url, data) {
return $.ajax({
type: "POST",
dataType: "xml",
url: url,
data: data,
async: true
});
};
var Plugin = function() {};
Plugin.prototype = {
update : function(options) {
/// other stuff
return Gateway.put($url, $data);
}
}
var Widget = new Plugin();
Widget.update(yourOptions).done(function() {
console.log('updated');
});
I really like the callback hell coding style, but sometimes it hurts. As suggested by other users, have you already heard about promises?
The core idea behind promises is that a promise represents the result of an asynchronous operation.
As suggested in the link above - that proposed a standard for them - once polyfill'd the browser using
<script src="https://www.promisejs.org/polyfills/promise-done-6.1.0.min.js"></script>
you will be able to create new Promise's, hence to use their nice done() attribute.
You will end up with
Plugin.prototype.update = function (options) {
return new Promise(function (fullfill, reject) {
Gateway.put($url, $data, function (data) {
fullfill(data);
});
});
};
That is, Plugin.prototype.update returns a promise.
Widget.update(...).done(function(data){
console.log('updated');
});
I have not tested the code, but the spirit is that. :)
EDIT: Using promises is awesome. I simply don't like when people discover them, use them in newer parts of the codebase but finally do not refactor the rest of it.

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