Nested context.executeQueryAsync with Deferred - javascript

How can I use nested context.executeQueryAsync with Deferred? Below is my code and I will explain what exactly I am looking for:
Code
function getValues() {
var dfd = $.Deferred(function () {
context.executeQueryAsync(function () {
var navigationItem = [];
// First Loop
while (termEnumerator.moveNext()) {
// Push Parent Terms in navigationItem array
navigationItem.push({ "name": ""});
// Get Sub Terms
context.executeQueryAsync(function () {
// Second Loop
while (termsEnum.moveNext()) {
// Push Sub Terms in navigationItem array
navigationItem.push({ "name": ""});
}
}, function (sender, args) {
console.log(args.get_message());
});
}
dfd.resolve(navigationItem);
}, function (sender, args) {
console.log(args.get_message());
dfd.reject(args.get_message());
});
});
return dfd.promise();
}
Basically I am trying to fetch Taxonomy (Terms & it's sub terms) in SharePoint Online using above code structure. Initially I have created an array named navigationItem and iterating through all the terms.
During iteration, first of all, I am pushing terms into this array and along with this, I am also getting it's sub terms if any and pushing it into the same array.
I want that code doesn't execute further until second loop completes it's execution. So that I will have final array while returning it to another function.

I want that code doesn't execute further until second loop completes
it's execution. So that I will have final array while returning it to
another function.
In this case, you need to have a defer for each executeQueryAsync.
Then, you need a create an overall defer to wait all of the async methods finished.
Here is the sample code for your reference:
(function ($) {
function executeQueryAsync(succeededCallback, failedCallback)
{
var period = Math.random() * 10000;
setTimeout(function () {
succeededCallback();
}, period);
}
function forEachAsync(items, funcAsync, callback)
{
var count = 0;
var total = $.Deferred();
function increment()
{
count++;
if(count == items.length)
{
total.resolve();
}
}
for (var i = 0; i < items.length; i++)
{
(function exec(item) {
var deferred = $.Deferred(function (defer) {
funcAsync(function () {
callback();
defer.resolve();
});
});
deferred.done(function () {
increment();
});
})(items[i]);
}
return total.promise();
}
var promise = forEachAsync([1, 2, 3, 4, 5], executeQueryAsync, function () {
console.log('call back executing + ' + Date.now());
});
promise.done(function () {
console.log("promise done");
});
})(jQuery);

Related

Node.js async.whilst() is not executing at all

I'm just trying to use async.whilst() as seen here.
Here is my simple code, taken from their docs:
var async = require('async');
console.log('start');
async.whilst(
function () { return true; },
function (callback) {
console.log('iteration');
callback();
},
function (err) {
console.log('end');
},
);
When I run this, the loop doesn't run. Only start is printed out.
Because you are returning true, that why the callback for function 1 wasn't called. So you only see the 'start'.
You can find some information below:
const async = require('async');
let count = 0;
const compareVariable = 10;
console.log('start');
async.whilst(
function functionName1(callbackFunction) {
// perform before each execution of iterFunctionInside, you need a condition(or other related condition) in 2nd params.
callbackFunction(null, count < compareVariable)
},
// this func is called each time when functionName1 invoked
function iterFunctionInside(callback) {
// increase counter to compare with compareVariable
count++;
console.log('iteration', count);
// if you want to show like tick time, you can set timeout here or comment out if you dont want
setTimeout(() => {
callback(null, count);
}, 1000)
},
function (err, n) {
console.log('end');
},
);

continue the loop after completion of the function called

Here I'm calling the function testlike() in each loop , the testlike function is called but before completion of that function the loop is completed ,after completion of loop inside the DataServices.GetLikes(value).then(function (resp1) ) function is executing , i want to continue the loop after completion of testlike() function in everytime calling
function getAllMessages()
{
DataServices.getAllMessages().then(function (resp) {
if (resp.d.results.length > 0) {
$.each(resp.d.results, function (key, val) {
testlike(val.Id);
});
}
});
}
function testlike(value)
{
DataServices.GetLikes(value).then(function (resp1) {
if (resp1.d.results.length > 0) {
$.each(resp1.d.results, function (k, v) {
if (value === v.MessageIdId) {
$(".eventfire").removeClass("likeevent").addClass("unlikeevent");
$(".unlikeevent").text("Unlike");
$(".unlikeevent").attr("unlikeitem", v.Id);
}
});
$(".unlikeevent").click(function () {
unLikeevent($(this).attr("unlikeitem"));
});
}
}, function (err) { alert(err) });
}
Your, testlike function is async, but your $.each loop is sync.
Not sure i understand you correctly but will try my best
first of all you don't need to check if the array item as any length. just loop over the array and if it's empty it won't do anything.
Second, you don't need to use jQuery's $.each loop. Arrays has methods for that: forEach and map
This will do all testlike in parallel and log all done when everything is complete
function getAllMessages() {
DataServices.getAllMessages().then(function(resp) {
// create a new array from results that are only promises
var jobs = resp.d.results.map(function(val) {
return testlike(val.Id)
})
// Wait for all to complete
Promise.all(jobs).then(function(results){
// It's done
console.log('all done')
})
})
}
function testlike(value) {
// here i return the value/promise
return DataServices.GetLikes(value).then(function (resp1) {
resp1.d.results.forEach(function (v, k) {
if (value === v.MessageIdId) {
$(".eventfire").removeClass("likeevent").addClass("unlikeevent")
$(".unlikeevent").text("Unlike").attr("unlikeitem", v.Id)
}
})
$(".unlikeevent").click(function () {
unLikeevent($(this).attr("unlikeitem"))
})
}, function (err) {
alert(err)
})
}
but if you want to do it one after the other, then i would change the getAllMessage to use async/await
async function getAllMessages() {
var resp = DataServices.getAllMessages()
for (let job of resp.d.results) {
await testlike(val.Id)
}
}
but there is alternative methods as well if you need a es5 version
just comment here if you need one and i will make something up

Asynchronous callbacks in a loop

I have a variable oldBindings which record all the existing bindings of an Excel table. I have built BindingDataChanged listeners based on oldBindings. So when newBindings come up, I need to remove all the old listeners linked to oldBindings and add new listeners based on newBindings. At the moment, I have written the following code:
var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions
function updateEventHandlers(newBindings) {
removeEventHandlers(oldBindings, function () {
addEventHandlers(newBindings)
})
}
function removeEventHandlers(oldBindings, cb) {
for (var i = 0; i < oldBindings.length; i++) {
Office.select("binding#"+oldBindings[i]).removeHandlerAsync(Office.EventType.BindingDataChanged, function (asyncResult) {
Office.context.document.bindings.releaseByIdAsync(oldBindings[i], function () {});
});
}
cb()
}
As removeHandlerAsync and releaseByIdAsync are built with callback rather than promise, I need to organise the whole code with callback. There are 2 things I am not sure:
1) in removeEventHandlers, will cb() ALWAYS be executed after the removal of all the listeners? How could I ensure that?
2) Do I have to make addEventHandlers as a callback of removeEventHandlers to ensure their execution order?
1) in removeEventHandlers, will cb() ALWAYS be executed after the removal of all the listeners?
No. It'll be called after the initiation of the removal. But if the removal is async, it may be called before the removal is complete.
2) Do I have to make addEventHandlers as a callback of removeEventHandlers to ensure their execution order?
Yes, but not the way you have. The way you have is just like doing
removeEventHandlers();
addEventHandlers();
because you call cb at the end of removeEventHandlers without waiting for anything to finish.
As removeHandlerAsync and releaseByIdAsync are built with callback rather than promise, I need to organise the whole code with callback.
Or you could give yourself Promise versions of them. More on that in a moment.
Using the non-Promise callback approach, to ensure you call cb from removeEventHandlers when all the work is done, remember how many callbacks you're expecting and wait until you get that many before calling cb:
var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions
function updateEventHandlers(newBindings) {
removeEventHandlers(oldBindings, function() {
addEventHandlers(newBindings);
});
}
function removeEventHandlers(oldBindings, cb) {
var waitingFor = oldBindings.length;
for (var i = 0; i < oldBindings.length; i++) {
Office.select("binding#"+oldBindings[i]).removeHandlerAsync(Office.EventType.BindingDataChanged, function (asyncResult) {
Office.context.document.bindings.releaseByIdAsync(oldBindings[i], function () {
if (--waitingFor == 0) {
cb();
}
});
});
}
}
But any time you have a callback system, you can Promise-ify it:
function removeHandlerPromise(obj, eventType) {
return new Promise(function(resolve, reject) {
obj.removeHandlerAsync(eventType, function(asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed) {
reject(asyncResult.error);
} else {
resolve(asyncResult.value);
}
});
});
}
function releaseByIdPromise(obj, value) {
return new Promise(function(resolve, reject) {
obj.releaseByIdAsync(value, function(asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed) {
reject(asyncResult.error);
} else {
resolve(asyncResult.value);
}
});
});
}
Then that lets you do this:
var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions
function updateEventHandlers(newBindings) {
removeEventHandlers(oldBindings).then(function() {
addEventHandlers(newBindings);
});
}
function removeEventHandlers(oldBindings) {
return Promise.all(oldBindings.map(function(binding) {
return removeHandlerPromise(Office.select("binding#"+binding), Office.EventType.BindingDataChanged).then(function() {
return releaseByIdPromise(Office.context.document.bindings, binding);
});
});
}
Or you can give yourself a generic Promise-ifier for any async op that returns an AsyncResult:
function promisify(obj, method) {
var args = Array.prototype.slice.call(arguments, 2);
return new Promise(function(resolve, reject) {
args.push(function(asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Failed) {
reject(asyncResult.error);
} else {
resolve(asyncResult.value);
}
});
obj[method].apply(obj, args);
});
}
Then that lets you do this:
var oldBindings = ["myBind1", "myBind2"]; // can be updated by other functions
function updateEventHandlers(newBindings) {
removeEventHandlers(oldBindings).then(function() {
addEventHandlers(newBindings);
});
}
function removeEventHandlers(oldBindings) {
return Promise.all(oldBindings.map(function(binding) {
return promisify(Office.select("binding#"+binding), "removeHandlerAsync", Office.EventType.BindingDataChanged).then(function() {
return promisify(Office.context.document.bindings, "releaseByIdAsync", binding);
});
});
}

JS: Get inner function arguments in asynchronous functions and execute callback

I try to write the function that returns all results of asynchronous functions and execute a callback that push into an array and log the result of every async function.
As a waiter that brings all dishes when they are all done.
I don't understand how to get the child arguments that should be returned as a result. The code of task and my not working solution is below:
The task:
var dishOne = function(child) {
setTimeout(function() {
child('soup');
}, 1000);
};
var dishTwo = function(child) {
setTimeout(function() {
child('dessert');
}, 1500);
};
waiter([dishOne, dishTwo], function(results) {
console.log(results); // console output = ['soup', 'dessert']
});
My not working solution:
function child(arg) {
this.arr.push(arg)
}
function waiter(funcArray, doneAll) {
var result = {
arr: []
};
let i = 0;
const x = child.bind(result)
funcArray.forEach(function(f) {
f(x)
i++;
if(i == 2) {
doneAll(result.arr)
}
});
}
Problem is this part:
funcArray.forEach(function(f) {
f(x)
i++;
if(i == 2) {
doneAll(result.arr)
}
});
which is a synchronous function so when you check if(i == 2), you basically check, that you have called all async functions, but they did not returned anything yet, so all you know is, that the functions have been called, but result.arr is not yet populated.
You must move the doneAll(result.arr) expression into child callback, then it will be called by async function as it returns result.
Simpliest solution I can think of is writing your child as
function child(arg) {
if (this.arr.push(arg) === this.allCount) this.doneAll(this.arr);
}
and in your waiter function enhance result object
var result = {
arr: []
, allCount: funcArray.length
, doneAll: doneAll
};
This shall work, but has one drawback -- position of results does not keep position of functions in funcArray, the position of results is sorted by duration of async function, simply the first resolved would take first result etc. If this is a problem, you must pass also index to your child function to store result at precious position in result array and then the check by arr.length would not work, because JS array returns length as the highest index + 1, so if your last funcArray would fulfill first, it'll fill last index and the length of result.arr will be equal to this.allCount, so for keeping order of result the same as funcArray, you will need to store number of returned results as another number, increase that number with every new result and compare that number to allCount.
Or decrease allCount like so
function child(idx, arg) {
this.arr[idx] = arg;
if (--this.allCount === 0) this.doneAll(this.arr);
}
And modify your waiter function
function waiter(funcArray, doneAll) {
const result = {
arr: []
, allCount: funcArray.length
, doneAll: doneAll
};
funcArray.forEach(function(f, i) {
f(child.bind(result, i));
});
}
Why not Promise?
function dishOne() {
return new Promise(function(resolve, reject) {
setTimeout(function() { resolve('soup') }, 1000)
})
}
function dishTwo() {
return new Promise(function(resolve, reject) {
setTimeout(function() { resolve('dessert') }, 1500)
})
}
Your waiter function:
function waiter(dishes, callback) {
return Promise.all(dishes).then(callback)
}
And you can use it like this
waiter([dishOne(), dishTwo()], function(results) {
// Invoked when all dishes are done
console.log(results) // ['soup', dessert']
})
Much easier to understand. Right?

Chaining more than two tasks using deferred and can this be use for loops?

I am getting used to $.Deferred just now, and it happens that I needed to chain three tasks using $.Deferred - then.
I created a function:
function processA(param1, param2) {
log = $.post('http://domain.com/process',
{
id: param1,
another: param2
}
),
set = log.then(function(html){
if (someCondition) {
console.log('Successful');
// i want to do another ajax call here
// and chain another, that does an ajax again
}
})
}
How do I do that, as stated in the comments of my code.
Is it right? Not tested, just thought while typing this.
set = log.then(function(html){
if (someCondition) {
console.log('Successful');
$.post(....),
def = set.then(function(data){
// i want to do another thing here.
})
}
})
And another thing, is it possible to use the function in a loop?
e.g.
data = [{param1:"...", param2:"..."}, {..., ...}]
$.each(data, function(k,v){
processA(v.param1, v.param2);
})
Here better explained chaining promises:
function authenticate() {
return getUsername()
.then(function (username) {
return getUser(username);
})
// chained because we will not need the user name in the next event
.then(function (user) {
return getPassword()
// nested because we need both user and password next
.then(function (password) {
if (user.passwordHash !== hash(password)) {
throw new Error("Can't authenticate");
}
});
});
}
This is something I wrote for a dice game. Rolling the dice, each dice.roll() gets stored into a $.Deferred object. When all dice have run their animation, you can execute your callbacks or whatever.
var promises = [];
// collect promises
_.each(fDice, function (die) {
promises.push(function () {
return $.Deferred(function (dfd) {
$(die).roll(options, function (data) {
dfd.resolve(data); // resolve the state
});
}).promise(); // send it back
});
});
// roll selected dice
dfrAll(promises).done(function () {
// do something here
});
dfrAll: function (array) {
/// <summary>Resolves n $.Deferred functions from an Array</summary>
/// <param name="Array" type="Array">An Array of $.Deferred functions</param>
/// <returns type="Deferred" />
var dfd = $.Deferred(),
len = array.length,
results = [];
if (len === 0) {
dfd.resolve(results);
} else {
for (var i = 0; i < len; i++) {
var promise = array[i];
$.when(promise()).then(function (value) {
results.push(value);
if (results.length === len) {
dfd.resolve(results);
}
});
}
}
return dfd.promise();
}
For me the storage of deferred functions into an array was the key to resolve my animations. Hope it helps.

Categories

Resources