Do something only after asynchronous call is finished - javascript

I'm trying to create a bounce effect on an image after an synchronous AND an asynchronous call but can't figure out how to do it. The problem I'm having now is that I sometimes get both the bounce effect and afterwards isExecuted becomes true because the asynchronous event takes some time.
My code should work like this:
Iterate over each object in myEnum and execute the following
if myCondition1 is equal too myCondition2 set isExecuted = true
if above is not true, call an asynchronous method which evaluates some stuff, if it's true it will set isExecuted = true.
wait for all above is finished, then if isExecuted still is false, bounce the image.
Here's the code:
var isExecuted = false;
myEnum.each()
{
if (myCondition1 == myCondition2) { //Synchronous
isExecuted = true;
return false; //stop the iteration
}
else {
MyAsyncFunction(myCondition2, function(isTrue) { // Asynchronous
if (isTrue) {
isExecuted = true;
return false; //stop the iteration
}
});
}
});
// Execute this ONLY when above code is finished executing
if (isExecuted == false) {
BounceImage();
}
Please note that the async function is not always executed but the bounce check must always be executed if isExecuted is true.

This whole setup won't work as you want because you cannot stop the iteration from the asynchronous callback. Instead you have to process the array (or whatever myEnum is) asynchronously and do something afterwards. I highly recommend to learn about promises.
function process(array, cb) {
var i = 0;
function p(v) {
return new Promise((resolve, reject) => {
try {
// call the callback, passing in the current value and
// a callback to control execution
cb(v, function next(stop, result) {
if (stop) {
// if stop = true, abort the iteration and resolve the passed in value
resolve(result);
} else if (i < array.length) {
// else if there are more elements left, process them
resolve(p(array[i++]));
} else { // else resolve to the passed in value
resolve(result);
}
});
} catch(e) {
reject(e);
}
});
}
// start with the first element
return p(array[0]);
}
process([1,2,3], function(v, next) {
if (v == 2) {
return next(true, v);
}
next();
}).then(result => console.log(result));
Applied to your code it would look something like
process(myEnum, function(v, next) {
if (myCondition1 == myCondition2) {
return next(true, true);
} else {
MyAsyncFunction(myCondition2, function(isTrue) {
if (isTrue) {
return next(true, true);
}
next();
});
}
}).then(function(isExecuted) {
if (!isExecuted) {
BounceImage();
}
});
Of course you can also use an existing library that allows you to do this. There a many different (potentially more elegant) ways to achieve this.

Instead, use callbacks:
function asyncFunction (a, b, c, callback) {
...
callback();
}
asyncFunction(1, 2, 3, function () {
doSomethingOnceDone();
});
This is a very common practice. It's how most async Chrome APIS do it, just to name one thing.

Related

NodeJS recursive call inside for loop, how to know when all calls are done?

I am writing a recursive function inside for loop like below:
var output = [];
function myFunc(myValue, callback) {
myAnotherFunc(myValue, function(result){
for (var i=0; i < result.myKey.length; i++){
if(result.myKey[i].name === 'something'){
myFunc(result.myKey[i].recurseValue, function(recursiveResult){
//some recursive stuff
output.push(recursiveResult.someValue)
});
}
}
});
}
And initiating the recursive function like below:
myFunc(initialValue, function(result){
//some stuff
});
Its working fine, but how do I know when my recursive flow ends so that I can do something else from the final output?
You can use Promises™! It's basically a way to defer a callback till after an Asynchronous flow is completed: Example:
// Instead of passing your normal callback, we'll tell the
// function to use resolve(results) to pass your results to the
// next code block so it can do something after all your recursions are completed
const someTask = new Promise(resolve => myFunc(initialValue, resolve))
someTask.then(result => {
/* Do Something with the results at the end of aformentioned callback hell :D */
})
PS. You also have to modify your original function signature to:
function myFunc(myValue, callback) {
myAnotherFunc(myValue, function(result){
const cbks = [] //Store the async resuls of all myFunc() executions
for (i=0; i < result.myKey.length; i++){
if(results[i] === 'something'){
cbks.push(new Promise(res => myFunc(result[i].recurseValue, res)))
}
}
//Run all async myFunc() and return the results in an array
Promise.all(cbks).then(callback)
});
}
function myFunc(resolve) {
var rec = function(myVal, cb) {
myOther(recurseValue, function(result) {
var hasName = result[myKey].filter(function(obj) {
return obj.name === 'something';
})[0];
if (hasName) {
rec(hasName[recurseValue], function(recResult) {
// other recursive stuff
});
} else {
resolve(?); // whatever the final value should be
}
});
};
return rec;
}
function recurseAsync(f, initial) {
return new Promise(function(resolve, reject) {
f(resolve)(initial);
});
}
Couple notes.
The recurseAsync function takes a function that takes a resolution callback and returns a recursive function that calls that callback when finished to resolve the promise. myFunc has been altered to fit that format.
I used array filtering rather than a for loop and shortened some names. Also if you are using a variable for object access use [] instead of .. To use the final value when all of this is finished you can call .then on the promise.
// whatever initial value 'foo' should be
var finished = recurseAsync(myFunc, foo);
finished.then(function(finalValue) {
// do something with the final result of all the recursion
});

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

How to do a "for" loop with asynchronous condition in Javascript?

I have this function:
waitForFreeAccnt.prototype.isMemberFree = function () {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return true;
} else {
console.log('it is not free');
return false;
}
});
};
I would like to wait till the account is free for up to 10 seconds with something like that:
var test = function () {
for (var start = 1; start < 10; start++) {
var result = self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
But it doesn't work because self.api.getMemberInfo is asynch call. This is super frustrating with Javascript. Any other language it would be so simple to do. How do I force the for loop to wait for self.isMemberFree() to finish executing before proceeding with the loop?
Also to note, this is not in browser execution so I don't care about anything hanging.
When dealing with asynchronous code, you need to make use of callbacks. That is, if you want to do a() and b() in order but a() does something asynchronously, then you need to call b() from within a() once a() has a result. So not:
a(); // does something asynchronously
b(); // tries to use a()'s result but it isn't available yet
... but rather
a(b); // pass b to a() and a() will call it when ready
function a(callback) {
triggerAsyncFunction(function(result) {
if (result === something)
callback("a just finished");
});
}
Note that a() doesn't refer to b() by name, it just calls whatever function is passed in as an argument.
So applying that to your code, maybe something like this:
waitForFreeAccnt.prototype.isMemberFree = function (cbf) {
var self = this;
self.api.getMemberInfo(function () {
cbf(self.api.connect.accountType === 'FREE');
});
};
waitForFreeAccnt.prototype.testMemberXTimes = function(maxAttempts, callback) {
var attempts = 0;
var self = this;
(function attempt() {
self.isMemberFree(function(free) {
if (free)
callback(true);
else if (++attempts < maxAttempts)
setTimeout(attempt, 1000);
else
callback(false);
});
)();
};
this.testMemberXTimes(10, function(isFree) {
// the next part of your code here, or called from here
// because at this point we know we've tested up to
// ten times and isFree tells us the result
});
Note that the way I coded getMemberInfo() it is basically doing the same thing yours was, but instead of returning a boolean it is calling the callback function and passing the same boolean value that you were returning. (I've removed the console.log()s to make the code shorter.)
Note also that you could structure the above to use promises, but the end result will be the same.
You could return a Promise
waitForFreeAccnt.prototype.isMemberFree = function () {
return new Promise((reject, resolve)=>
// set a timeout if api call takes too long
var timeout = setTimeout(()=> reject(Error('API timeout')), 10000);
// make api call
this.api.getMemberInfo(()=> {
clearTimeout(timeout);
resolve(this.api.connect.accountType === 'FREE');
});
);
};
Then use it like this
whatever.isMemberFree().then(isFree=> {
if (isFree)
console.log('it is free');
else
console.log('it is not free');
})
// handle timeout or other errors
.catch(err=> {
console.log(err.message);
});
Building on naomik's answer, if you do it that way you can pretty easily use a for loop with it, using the (most likely) upcoming async/await feature - though it's not part of ES2015.
// Note "async" here! That will make "await" work. It makes the function
// return a promise, which you'll be able to either "await" or
// "test().then" later.
var test = async function () {
for (var start = 1; start < 10; start++) {
// Right here we're using "await" - it makes JavaScript *wait* for
// the promise that comes from self.isMemberFree() to be finished.
// It's really handy because you can use it in loops like "for" and
// "while" without changing the flow of your program!
var result = await self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
For now you'll need to use a transpiler like Babel or Traceur before you can really use async/await, though. It's only supported in Microsoft Edge 14 right now.
And a big emphasis that what is returned from test() isn't whatever you directly return from inside it. If I do this:
var test = async function() { return 15; };
var result = test();
I'm not going to get 15 - I'll get a promise that will resolve as 15:
result.then(function(res) {
console.log(res); // 15
});
// or, use an async function again:
var main = async function() {
console.log(await res); // 15
};
main();
I don't have my work laptop today because it is Sunday, I'm coding this on sublime. Apologise if the syntax is a bit off.
To solve your problem I would recommend changing isMemberFree() to take in a callback function. This is because isMemberFree is async, and you will need a way to report the result after it has done the work.
Then change test function to use setTimeout API to wait a second.
Wrap the function call for isMemberFree() to be in a nested function and call it recursively, that way you'll have synchronize control over the async calls.
Look at the coding example:
waitForFreeAccnt.prototype.isMemberFree = function (done) {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return done(null, true);
} else {
console.log('it is not free');
return done(null, false);
}
});
};
var test = function () {
var testMembership = function(waitAttempt, isFree) {
if (isFree) {
return;
}
else if (waitAttempt > 10) {
// wait exceeded, do something.
return;
}
setTimeout(function() {
self.isMemberFree(function(err, isFree) {
testMembership(waitAttempt+=1, isFree);
});
}, /*total milliseconds in 1sec=*/1000);
}
testMembership(/*WaitAttempts=*/0, /*isFree=*/false);
};
What the above code does is that, presumably something has already been done to the member's account and now test function is called. So it waits for 1 second, then call isMemberFree function, this happens recursively until either isMemberFree() returns true OR the 10 seconds wait has been exceeded.

Chaining callbacks in a custom-made for-each loop, supporting both synchronous and asynchronous functions

I have a for_users function that gets an array of users from a web service, executes a passed function f on the received array, then calls a continuation f_then callback.
// Execute f on every user, then f_then.
function for_users(f, f_then)
{
// Get all users from the database, in user_array
db.get_all_users(function(user_array)
{
// Execute f on every user
user_array.forEach(f);
// Call continuation callback
f_then();
});
}
When calling for_users, passing an asynchronous function as the f parameter, I would like all the f callbacks to end before calling f_then. This is obviously not happening in the current code, as user_array.forEach(f) does not wait for f to finish before starting the next iteration.
Here's an example of a problematic situation:
function example_usage()
{
var temp_credentials = [];
for_users(function(user)
{
// Get credentials is an asynchronous function that will
// call the passed callback after getting the credential from
// the database
database.get_credentials(user.ID, function(credential)
{
// ...
});
}, function()
{
// This do_something call is executed before all the callbacks
// have finished (potentially)
// temp_credentials could be empty here!
do_something(temp_credentials);
});
}
How can I implement for_users such that if f is an asynchronous function, f_then is called only when all f functions are completed?
Sometimes, though, the passed f to for_users is not asynchronous and the above implementation could suffice. Is there a way to write a generic for_users implementation that would work as intended both for asynchronous and synchronous f functions?
this should work for you:-
function for_users(f, f_then) {
db.get_all_users(function(user_array) {
var promises = [];
user_array.forEach(function(user) {
promises.push(new Promise(function(resolve, reject) {
f(user);
resolve();
}));
});
if (f_then)
Promise.all(promises).then(f_then);
else
Promise.all(promises);
}
});
}
Simple Test below:-
function for_users(f, f_then) {
var users = [{ID: 1}, {ID: 2}, {ID: 3}];
var promises = [];
users.forEach(function(user) {
var promise = new Promise(function(resolve, reject) {
f(user);
resolve();
});
promises.push(promise);
})
if (f_then)
Promise.all(promises).then(f_then);
else
Promise.all(promises)
}
for_users(function(user) {
console.log(user.ID);
}, function() {
console.log('finshed')
})
You can add next continuation callback to f function like this:
function for_users(f, f_then) {
// Get all users from the database, in user_array
db.get_all_users(function(user_array) {
// Execute f on every user
(function recur(next) {
var user = user_array.shift();
if (user) {
f(user, function() {
recur(next);
});
} else {
// Call continuation callback
next();
}
})(f_then);
});
}
and then you will be able to call this function using this:
for_users(function(user, next) {
// Get credentials is an asynchronous function that will
// call the passed callback after getting the credential from
// the database
database.get_credentials(user.ID, function(credential) {
next();
});
}, function() {
// This do_something call is executed before all the callbacks
// have finished (potentially)
// temp_credentials could be empty here!
do_something(temp_credentials);
});
var getCredentials = function(step){
return function(user){
database.get_credentials(user.ID, function(credential) {
step(credential);
});
};
};
var allFinish = function(f){
return function(step) {
return function(arr){
var finished = 0;
var values = new Array(arr.length);
if(arr.length){
arr.forEach(function(el, i){
f(function(value){
if(finished === arr.length){
step(values);
} else {
values[i] = value;
finished++;
}
})(el);
});
} else {
step(values);
}
};
};
};
var forEachUser = function(doSomething){
db.get_all_users(allFinish(getCredentials)(doSomething));
}
And then you can just simply do:
forEachUser(function(tempCredentials){
//tempCredentials === allCredentials
});
There's probably better ways to handle the order of values inserted in the array in allFinish. allFinish works by taking a function that takes a step and calling it with a step function that will call another step function when all calls are finished. I curried the functions, but it isn't really necessary. It's just a convenience.

Javascript: Calling a delayed function inside another delayed function

So, I have a delayed function called delayed1 that gets repeated everytime cond1 is true, the problem is, somewhere inside that delayed1 function do_something1() gets called and if cond3 is true, another delayed function called delayed2 gets called.
Now the problem is I want delayed1 to wait until delayed2 is finished (that means until cond2 is false) and then resume again, which doesn't seem to be happening. Once delayed2 gets started, delayed1 doesn't wait for it to finish and then resume. Is there a way to do this? Hopefully I was clear enough...
function delayed2() {
setTimeout(function () {
do_something2();
if ( cond2 ) {
delayed2();
}
}, 1000)
}
function do_something1(){
if ( cond3 ){
delayed2();
}
else {
something_else();
}
}
function delayed1() {
does_something_here();
setTimeout(function () {
do_something1();
if ( cond1 ){
delayed1();
}
}, 1000)
}
I see what your are trying to do here. You may probably want to approach in a different way. The best way would be to use promises or Deferreds.
Here is the example using jquery deferreds of your given code.
var wait_a_second = $.Deffered();
$.when(wait_a_second)
.then(does_something_here)
.then(function(){
if( cond1 ) delayed1();
})
.then(delayed1);
function delayed1(){
var d = $.Deffered()
// Your conditions and logic
if( cond ) {
d.resolve();
}
return d.promise();
}
function delayed2(){
var d = $.Deffered();
// Your conditions and logic
if( cond ) {
d.resolve();
}
return d.promise();
}
In short learn promises to manage async operations queue.
You should use promises to implement synchronous execution in JS.
Promise is a mechanism which returns an object to which you can hook callbacks. These callbacks can be called once a specific function is done with execution. Here is how it applies to your situation.
You generally have 3 functions which depend on each other. The first one should wait for the second and the second one should wait for the third. In the first level the second function should return a Promise.
function delayed1() {
does_something_here();
setTimeout(function () {
var promise = do_something1();
promise.then(function(){
if ( cond1 ){
delayed1();
}
});
}, 1000)
}
Here is your second function. It creates a promise and returns it. Also inside its if ( cond3 ) it should wait for a promise from the third function. I'm not going to write your third function as I'm certain that you can do it yourself following this pattern.
function do_something1(){
return new Promise(function(resolve){
if ( cond3 ){
var promise = delayed(2);
promise.then(function(){
resolve()
})
}
else {
something_else();
resolve();
}
})
}
You can try using callbacks. Following is a basic representation:
var count = 0;
function delay1(callback) {
setTimeout(function() {
console.log("Delay 1");
count++;
if (count < 12) {
if (count % 2 == 0) {
delay1();
} else {
delay2()
}
}
if (callback) {
callback();
}
}, 1000);
}
function delay2(callback) {
setTimeout(function() {
console.log("Delay 2");
if (count > 10) {
delay1(function() {
console.log("This is callback;")
})
} else if (count % 2 == 0) {
delay2();
} else {
delay1()
}
if (callback) {
callback();
}
count++;
}, 1000);
}
delay1();

Categories

Resources