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

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?

Related

Synchronous way of handling asynchronous function, two level deep

I am looping over an array to update its values using returned value from called function which internally calls an asynchronous function.
I need to handle asynchronous function in synchronous way which is not being directly called. This is replication of scenario.
function condition(){
// Code of this function is not accessible to me.
return new Promise(function(resolve, reject){
setTimeout(function(){
if(parseInt(Math.random() * 100) % 2){
resolve(true);
}
else{
reject(false)
}
}, 1000)
});
}
async function delayIncrease(value){
var choice = await condition();
if(choice) { return ++value; }
else { return --value; }
}
// Start calling functions
dataArr = [1,2,3,4,5];
for(var i in dataArr){
dataArr[i] = delayIncrease(dataArr[i]);
}
If possible, I would like to have the solution in above structure mentioned.
I have achieved the desired result by adding other function and passing "index" + "new_value" as parameters. This function directly modifies original array and produces desired result. Working example.
function condition(){
// Code of this function is not accessible to me.
return new Promise(function(resolve, reject){
setTimeout(function(){
if(parseInt(Math.random() * 100) % 2){
resolve(true);
}
else{
reject(false)
}
}, 1000)
});
}
function delayIncrease(value, index){
condition().then(
function(){ updateData(++value, index) },
function(){ updateData(--value, index) }
)
}
function updateData(value, index){
dataArr[index] = value;
}
dataArr = [1,2,3,4,5];
for(var i in dataArr){
dataArr[i] = delayIncrease(dataArr[i], i);
}
Please provide solution for this requirement in best possible way. Possible solution in Angular 4 way is also appriciated. I thought of writing it in normal JavaScript form as Observables behave nearly same.
I followed this Medium page and http://exploringjs.com
Your condition function does not really fulfill the promise with either true or false, it does randomly fulfill or reject the promise. Instead of branching on a boolean, you will need to catch that "error":
async function delayIncrease(value) {
try {
await condition();
return ++value;
} catch(e) {
return --value;
}
}
You could do something like this:
var condition = async () =>
(parseInt(Math.random() * 100) % 2)
? true
: false
var delayIncrease = async (value) =>
(await condition())
? ++value
: --value
var dataArr = [1, 2, 3, 4, 5];
// Start calling functions
Promise.all(
dataArr.map(
delayIncrease
)
)
.then(
resolve => console.log("results:",resolve)
,reject => console.warn("rejected:",reject)
)
Once something is async you have to make the entire call stack prior to that function async. If a function calls an async function that that function returns an async value and so does the one calling it and calling it and calling it ...
More info on javascript async and why can be found here.
Since the example provided doesn't have any async api's in there you don't need to do it async:
var condition = () =>
(parseInt(Math.random() * 100) % 2)
? true
: false
var delayIncrease = (value) =>
(condition())
? ++value
: --value
var dataArr = [1, 2, 3, 4, 5];
// Start calling functions
dataArr.map(
delayIncrease
)
[update]
When you mutate an array of objects and cosole.log it you may not see the values as they actually were when you log it but you see the values as they are right now (this is a "bug" in console.log).
Consider the following:
var i = -1,arr=[];
while(++i<1){
arr[i]={};
arr[i]["name"+i]=i
}
var process = (index) =>
arr[index]["name"+index]++;
arr.forEach(
(item,index) =>
Promise.resolve(index)
.then(process)
);
console.log("obj at the moment you are looking at it:",arr)
console.log("obj at the moment it is logged:",JSON.stringify(arr))
When you expand obj at the moment you are looking at it you see that name0 property of the first element changed to 1.
However; look at obj at the moment it is logged: and see the actual value of the first element in the array. It has name0 of 0.
You may think that the that code runs asynchronous functions in a synchronous way by mutating the object(s) in an array, but you actually experience a "bug" in console.log

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

Array.forEach Callback at the end

I'm using Array.forEach function of javascript to get a different message for each element of a list. So I'm using forEach function and I'm looking for a way to execute my callback function cb(result) when the foreach as finished to both execute .forEach and msgAfterTimeout. I read that there was something called promises but I don't really get how I can use them here.
function msgAfterTimeout (who, timeout, onDone) {
setTimeout(function () {
onDone(" Hello " + who + "!");
}, timeout);
}
var test="";
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}];
function dispName(cb)
{
list.forEach(function(item, index)
{
msgAfterTimeout(item.name, 200, function (msg)
{
test=msg+"\n";
});
cb(result);
});
}
dispName(function(data){
console.log(data);
});
Here's your example with Promises:
var list = [{name: "foo", surname: "bar"}, {name: "Jean", surname: "dupond"}];
function msgAfterTimeout(who, timeout) {
return new Promise(resolve =>
setTimeout(
() => resolve(" Hello " + who + "!"),
timeout)
);
}
Promise.all(
list.map(item => msgAfterTimeout(item.name, 200))
).then(
result => console.log(result.join('\n'))
);
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
function msgAfterTimeout (who, timeout, onDone) {
setTimeout(function () {
onDone(" Hello " + who + "!");
}, timeout);
}
var test= "";
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}];
function newFunc(list, i, endCb) {
var name = list[i].name;
msgAfterTimeout(name, 200, function(msg) {
var test = msg + "\n";
console.log(test);
if(i+1 == list.length) {
endCb();
}
else {
newFunc(list, i+1, endCb);
}
});
}
function dispName(cb)
{
newFunc(list, 0 , cb);
}
dispName(function(){
console.log("Done with all!");
});
This outputs:
Hello foo!
Hello Jean!
Done with all!
forEach processes things in series, so for the sake of this answer, let's assume that's a requirement. Promise.all as demonstrated by #georg will process the items in parallel – it's a good answer but it won't help you if a series is what you actually need.
The Array.prototype methods reduce, map, and forEach are synchronous, but you could easily make an asynchronous variants. You're using forEach here but you're actually performing a reduce by hand. So we'll start by implementing an asynchronous reducek and then go from there
Don't worry too much about understanding reducek itself. All you need to know is the primary difference between reducek and reduce is that the reducing function receives an extra callback argument that is called when each item is done, and reducek has its own callback for when the entire input list is done.
function reducek (f, acc, [x, ...xs], k) {
if (x === undefined)
return k (acc)
else
return f (acc, x, y => reducek (f, y, xs, k))
}
function msgAfterTimeout (who, timeout, onDone) {
return setTimeout(onDone, timeout, "Hello " + who + "!")
}
function messageReducer (acc, item, done) {
return msgAfterTimeout(item.name, 200, function (text) {
return done([...acc, text])
})
}
function displayName (list, done) {
// use our async reduce here
return reducek (messageReducer, [], list, done)
}
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}]
displayName (list, function (texts) {
console.log("Done!", texts.join(' '))
})
// Done! Hello foo! Hello Jean!
Important things to notice ...
No longer performing reduce by hand – instead of using a test variable initialized with state of '' (empty string), reducek accepts the initial value as an argument.
Here we're using an initial state of of [] (empty array) to store each of the texts. When we're done, we join the texts together using texts.join(' ').
Not Another Ceremony ...
All of that is a lot of ceremony and continuations are not necessarily the best for asynchronous flow control. In fact, Promises were brought to JavaScript because a better tool was needed.
// instead of returning a timeout, return a Promise
function msgAfterTimeout (who, timeout) {
return new Promise(function (resolve) {
setTimeout(resolve, timeout, "Hello " + who + "!")
})
}
// new async/await syntax - work with Promises in a most elegant fashion
// no more callback parameter; async implicitly returns a Promise
async function displayName (list) {
let texts = []
for (let item of list)
texts.push(await msgAfterTimeout(item.name, 200))
return texts
}
var list = [{name:"foo",surname:"bar"},{name:"Jean",surname:"dupond"}]
// instead of a callback, chain a .then on the returned Promise
displayName(list).then(texts => console.log("Done!", texts.join(' ')))
// Done! Hello foo! Hello Jean!
Note: If you need to support older browsers, async/await needs to be transpiled using something like Babel.

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.

Updating an array inside a call back

I have the following two functions:
var abc;
function updateNum() {
abc=0;
g.dbm.transaction("leagues").objectStore("leagues").openCursor(null, "prev").onsuccess = function (event) {
var teams, i;
team.filter({
attrs: ["tid", "abbrev", "region", "name", "cid"],
seasonAttrs: ["winp", "playoffRoundsWon"],
season: g.season
}, function (teams) {
// Sort teams by playoffs and winp, for first round
teams.sort(function (a, b) {
if (a.playoffRoundsWon < b.playoffRoundsWon) {
return -1;
}
if (a.playoffRoundsWon > b.playoffRoundsWon) {
return 1;
}
return a.winp - b.winp;
});
abc+=1;
});
};
}
function getNum() {
return abc;
}
What I am trying to do is update the variable abc inside the callback function and then return it. I do this by first calling the updateNum() function in another file. Then I assign a variable to the value of getNum()
Here is how a sample code would look like:
myFile.updateNum();
var number = myFile.getNum();
I am currently unable to return the updated value of num. number keeps returning 0 (the default value) instead of the newly updated value (which is 1).
How can I get it to show an updated value? Please let me know if I need to add any more information.
Well, if updateNum is async, it would have to take a callback as argument so that you can be notified when the number was updated.
E.g.
var num = 0;
function updateNumAsync(callback) {
setTimeout(function () {
num = 1;
callback && callback(num); //call the callback if provided
}, 500);
}
updateNumAsync(function (num) {
console.log(num); //updated num
});
Here is a general pattern for using an asynchronous function with a callback to pass the asynchronous results around. What is team.filter? You will need to design your code such that the asynchronous portion calls a callback() function that was passed to the enclosing function.
If filtering gives you problems you may want to look at https://github.com/caolan/async#filterarr-iterator-callback
(function main(){
getNum(function(err, abc){
console.log('thx for playing '+abc)
});
})();
function getNum(anotherCallback) {
// Whatever code relies on the result of an asynchronous function must be
// placed inside the callback function
countTeams(function(abc){
console.log('countTeams completed, abc='+abc);
var err = null;
anotherCallback(err, abc);
});
};
function countTeams(callback){
var abc=0;
g.dbm.transaction("leagues").objectStore("leagues").openCursor(null, "prev").onsuccess = function (event) {
var teams, i;
// I don't know what this filter function does, I am assuming it's synchronous
team.filter({
attrs: ["tid", "abbrev", "region", "name", "cid"],
seasonAttrs: ["winp", "playoffRoundsWon"],
season: g.season
}, function (teams) {
// Sort teams by playoffs and winp, for first round
teams.sort(function (a, b) {
if (a.playoffRoundsWon < b.playoffRoundsWon) {
return -1;
}
if (a.playoffRoundsWon > b.playoffRoundsWon) {
return 1;
}
return a.winp - b.winp;
});
abc+=1;
});
return callback(abc); // 0 or n depending on what team.filter does
};
};

Categories

Resources