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.
Related
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
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?
Thats how I do it:
function processArray(array, index, callback) {
processItem(array[index], function(){
if(++index === array.length) {
callback();
return;
}
processArray(array, index, callback);
});
};
function processItem(item, callback) {
// do some ajax (browser) or request (node) stuff here
// when done
callback();
}
var arr = ["url1", "url2", "url3"];
processArray(arr, 0, function(){
console.log("done");
});
Is it any good? How to avoid those spaghetti'ish code?
Checkout the async library, it's made for control flow (async stuff) and it has a lot of methods for array stuff: each, filter, map. Check the documentation on github. Here's what you probably need:
each(arr, iterator, callback)
Applies an iterator function to each item in an array, in parallel. The iterator is called with an item from the list and a callback for when it has finished. If the iterator passes an error to this callback, the main callback for the each function is immediately called with the error.
eachSeries(arr, iterator, callback)
The same as each only the iterator is applied to each item in the array in series. The next iterator is only called once the current one has completed processing. This means the iterator functions will complete in order.
As pointed in some answer one can use "async" library. But sometimes you just don't want to introduce new dependency in your code. And below is another way how you can loop and wait for completion of some asynchronous functions.
var items = ["one", "two", "three"];
// This is your async function, which may perform call to your database or
// whatever...
function someAsyncFunc(arg, cb) {
setTimeout(function () {
cb(arg.toUpperCase());
}, 3000);
}
// cb will be called when each item from arr has been processed and all
// results are available.
function eachAsync(arr, func, cb) {
var doneCounter = 0,
results = [];
arr.forEach(function (item) {
func(item, function (res) {
doneCounter += 1;
results.push(res);
if (doneCounter === arr.length) {
cb(results);
}
});
});
}
eachAsync(items, someAsyncFunc, console.log);
Now, running node iterasync.js will wait for about three seconds and then print [ 'ONE', 'TWO', 'THREE' ]. This is a simple example, but it can be extended to handle many situations.
As correctly pointed out, you have to use setTimeout, for example:
each_async = function(ary, fn) {
var i = 0;
-function() {
fn(ary[i]);
if (++i < ary.length)
setTimeout(arguments.callee, 0)
}()
}
each_async([1,2,3,4], function(p) { console.log(p) })
The easiest way to handle async iteration of arrays (or any other iterable) is with the await operator (only in async functions) and for of loop.
(async function() {
for(let value of [ 0, 1 ]) {
value += await(Promise.resolve(1))
console.log(value)
}
})()
You can use a library to convert any functions you may need which accept callback to return promises.
In modern JavaScript there are interesting ways to extend an Array into an async itarable object.
Here I would like to demonstrate a skeleton of a totally new type AsyncArray which extends the Array type by inheriting it's goodness just to become an async iterable array.
This is only available in the modern engines. The code below uses the latest gimmicks like the private instance fields and for await...of.
If you are not familiar with them then I would advise you to have a look at the above linked topics in advance.
class AsyncArray extends Array {
#INDEX;
constructor(...ps){
super(...ps);
if (this.some(p => p.constructor !== Promise)) {
throw "All AsyncArray items must be a Promise";
}
}
[Symbol.asyncIterator]() {
this.#INDEX = 0;
return this;
};
next() {
return this.#INDEX < this.length ? this[this.#INDEX++].then(v => ({value: v, done: false}))
: Promise.resolve({done: true});
};
};
So an Async Iterable Array must contain promises. Only then it can return an iterator object which with every next() call returns a promise to eventually resolve into an object like {value : "whatever", done: false} or {done: true}. So basically everything returned is a promise here. The await abstraction unpacks the value within and gives it to us.
Now as I mentioned before, this AsyncArray type, since extended from Array, allows us to use those Array methods we are familiar with. That should simplify our job.
Let's see what happens;
class AsyncArray extends Array {
#INDEX;
constructor(...ps){
super(...ps);
if (this.some(p => p.constructor !== Promise)) {
throw "All AsyncArray items must be a Promise";
}
}
[Symbol.asyncIterator]() {
this.#INDEX = 0;
return this;
};
next() {
return this.#INDEX < this.length ? this[this.#INDEX++].then(v => ({value: v, done: false}))
: Promise.resolve({done: true});
};
};
var aa = AsyncArray.from({length:10}, (_,i) => new Promise(resolve => setTimeout(resolve,i*1000,[i,~~(Math.random()*100)])));
async function getAsycRandoms(){
for await (let random of aa){
console.log(`The Promise at index # ${random[0]} gets resolved with a random value of ${random[1]}`);
};
};
getAsycRandoms();
For modern Node.js:
To iterate through a collection truly asynchronously, you can try my tiny package with zero dependencies, compatible with ESM and CJS modules with .d.ts typings. Check the code it's really tiny.
https://www.npmjs.com/package/array-to-async-iterable
You can use it just like this:
for await(const el of new AsyncTimeIterator(arrayOfObjects)){
...
}
You can't just use for await of loop because of the JavaScript engines' microtasks and macrotasks nature.
In a brief, you won't get new HTTP requests and let other timers' callbacks to be executed with this code:
for await(const el of array){
...
}
You force V8 or the other engine to execute all the microtasks (your loop iteration) and when the loop completes you'll unblock the event loop and be ready to receive HTTP connections. So this code is completely useless.
How can I perform dynamic chaining in Javascript Promises, all the time I have seen only hardcoding of the calls for eg., (promise).then(request/functionName).then(request/functionName)
Given an array functions that all return promises, you can use reduce() to run them sequentially:
var myAsyncFuncs = [
function (val) {return Promise.resolve(val + 1);},
function (val) {return Promise.resolve(val + 2);},
function (val) {return Promise.resolve(val + 3);},
];
myAsyncFuncs.reduce(function (prev, curr) {
return prev.then(curr);
}, Promise.resolve(1))
.then(function (result) {
console.log('RESULT is ' + result); // prints "RESULT is 7"
});
The example above uses ES6 Promises but all promise libraries have similar features.
Also, creating the array of promise returning functions is usually a good candidate for using map(). For example:
myNewOrmModels.map(function (model) {
return model.save.bind(model);
}).reduce(function (prev, curr) {
return prev.then(curr);
}, Promise.resolve())
.then(function (result) {
console.log('DONE saving');
});
One option is to utilize the properties of objects and the ability to invoke them via strings.
I wrote a small sample Here and posted it below.
The idea is that you have the set of functions that you wish to run set in some namespace or object, as I did in 'myNamespace':
myNamespace = {
"A": function() {return "A Function";},
"B": function() {return "B Function";},
"C": function() {return "C Function";}
}
Then your main promise would run and somehow (via inputs, ajax, prompts, etc.) you would get the string value of the function you want to have run, which isn't known until runtime:
My main promise uses a prompt to get a letter from the user:
var answer = prompt('Starting. Please pick a letter: A,B,C');
if(myNamespace[answer] === undefined)
{
alert("Invalid choice!");
reject("Invalid choice of: " + answer);
}
else
{
resolve(answer);
}
In the next 'then' I use that value (passed via the resolve function) to invoke the function:
.then(function(response) {
funcToRun = myNamespace[response]();})
Finally, I output to html the result of my dynamic function call and I use some recursive fun to make it more interactive and demonstrate that it is dynamic:
.then(function(){
document.getElementById('result').innerHTML = funcToRun;})
.then(function(){
if(prompt("Run Again? (YES/NO)")==="YES")
{
doWork();
}
});
myNamespace = {
"A": function() {return "A Function";},
"B": function() {return "B Function";},
"C": function() {return "C Function";}
}
function doWork()
{
var funcToRun;
new Promise(function(resolve,reject) {
var answer = prompt('Starting. Please pick a letter: A,B,C');
if(myNamespace[answer] === undefined)
{
alert("Invalid choice!");
reject("Invalid choice of: " + answer);
}
else
{
resolve(answer);
}
})
.then(function(response) {
funcToRun = myNamespace[response]();})
.then(function(){
document.getElementById('result').innerHTML = funcToRun;})
.then(function(){
if(prompt("Run Again? (YES/NO)")==="YES")
{
doWork();
}
});
}
doWork();
<div id="result"></div>
Since promises unwrap, just continue to add then statements and it will continue to be chained together
function asyncSeries(fns) {
return fns.reduce(function(p, fn) {
return p.then(fn);
}, Promise.resolve());
}
Recursively is a pretty cool way to do it as well :)
function countTo(n, sleepTime) {
return _count(1);
function _count(current) {
if (current > n) {
return Promise.resolve();
}
return new Promise(function(resolve, reject) {
console.info(current);
setTimeout(function() {
resolve(_count(current + 1));
}, sleepTime);
});
}
}
This is ES7 way.
Let's say you have multiple promises defined in an array.
var funcs = [
_ => new Promise(res => setTimeout(_ => res("1"), 1000)),
_ => new Promise(res => setTimeout(_ => res("2"), 1000))
}
And you want to call like this.
chainPromises(funcs).then(result => console.log(result));
You can use async and await for this purpose.
async function chainPromises(promises) {
for (let promise of promises) { // must be for (.. of ..)
await promise();
}
}
This will execute the given functions sequentially(one by one), not in parallel. The parameter promises is an array of functions, which return Promise.
Plunker: http://plnkr.co/edit/UP0rhD?p=preview
I think the simplest way is:
const executePromises = function(listOfProviders){
const p = Promise.resolve(null);
for(let i = 0; i < listOfProviders.length; i++){
p = p.then(v => listOfProviders[i]());
}
return p;
};
I believe the above is basically equivalent to:
const executePromises = async function(listOfProviders) {
for(let i = 0; i < listOfProviders.length; i++){
await listOfProviders[i]();
}
};
This solution based on usage promises of introduced in the EcmaScript 6 (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), so before use it see table browser`s support https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise#Browser_compatibility
Code
var f1 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function1 is done');
}
var f2 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function2 is done');
}
var f3 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function3 is done');
}
var f4 = function(){
for (var i = 0; i < 800000000; i++) {}
console.log('Function4 is done');
}
callbacks = function(){
// copy passed arguments
var callbacks = arguments;
// create array functions
var callbacks = Object.keys(callbacks).map(function(el){ return callbacks[el] });
var now = Date.now();
callbacks.reduce(function(previousPromise, currentFunc){
return previousPromise.then(
function(){
currentFunc();
var seconds = (Date.now() - now) / 1000;
console.log('Gone', seconds, 'seconds');
}
)
}, Promise.resolve());
}
callbacks(f1, f2, f3, f4);
Result in Chrome console (values seconds will be different):
Function1 is done
Gone 1.147 seconds
Function2 is done
Gone 2.249 seconds
Function3 is done
Gone 3.35 seconds
Function4 is done
Gone 4.47 seconds
Notes:
It is does not work if a function contains a timer (for this problem
I try also jQuery`s $Callbacks, $.Ajax and $.When but it not help.
The only decision, what I found, usage resolve() in callback of a
timer, but it is not acceptable if you have completed functions.).
Testing environment
$ google-chrome --version
Google Chrome 53.0.2785.116
I just had a problem with my api provider that doing Promise.all() would end up in concurrency db problems..
The deal with my situation is that i need to get every promise result in order to show some "all ok" or "some got error" alert.
And i don't know why .. this little piece of code who uses reduce when the promises resolved i couldn't get my scope to work (too late to investigate now)
$scope.processArray = function(array) {
var results = [];
return array.reduce(function(p, i) {
return p.then(function() {
return i.then(function(data) {
results.push(data);
return results;
})
});
}, Promise.resolve());
}
So thanks to this post http://hellote.com/dynamic-promise-chains/ I came with this little bastard.. It's not polished but it's working all right.
$scope.recurse = function(promises, promisesLength, results) {
if (promisesLength === 1) {
return promises[0].then(function(data){
results.push(data);
return results;
});
}
return promises[promisesLength-1].then(function(data) {
results.push(data);
return $scope.recurse(promises, promisesLength - 1, results);
});
}
Then i invoke the function like this:
var recurseFunction = $scope.recurse(promises, promises.length, results);
recurseFunction.then(function (response) { ... });
I hope it helps.
To build on this answer, if you want to return the result of all those promises, too:
async function chainPromises(promises) {
let results = [];
for (let promise of promises) {
let result = await promise();
results.push(result);
}
return results;
}
Check the following tutorial for
programmatic (dynamic) chaining of javascript/node.js promises and
Promise chaining using recursive functions
Programmatic-Chaining-and-Recursive-Functions-with-JavaScript-Promise
Thats how I do it:
function processArray(array, index, callback) {
processItem(array[index], function(){
if(++index === array.length) {
callback();
return;
}
processArray(array, index, callback);
});
};
function processItem(item, callback) {
// do some ajax (browser) or request (node) stuff here
// when done
callback();
}
var arr = ["url1", "url2", "url3"];
processArray(arr, 0, function(){
console.log("done");
});
Is it any good? How to avoid those spaghetti'ish code?
Checkout the async library, it's made for control flow (async stuff) and it has a lot of methods for array stuff: each, filter, map. Check the documentation on github. Here's what you probably need:
each(arr, iterator, callback)
Applies an iterator function to each item in an array, in parallel. The iterator is called with an item from the list and a callback for when it has finished. If the iterator passes an error to this callback, the main callback for the each function is immediately called with the error.
eachSeries(arr, iterator, callback)
The same as each only the iterator is applied to each item in the array in series. The next iterator is only called once the current one has completed processing. This means the iterator functions will complete in order.
As pointed in some answer one can use "async" library. But sometimes you just don't want to introduce new dependency in your code. And below is another way how you can loop and wait for completion of some asynchronous functions.
var items = ["one", "two", "three"];
// This is your async function, which may perform call to your database or
// whatever...
function someAsyncFunc(arg, cb) {
setTimeout(function () {
cb(arg.toUpperCase());
}, 3000);
}
// cb will be called when each item from arr has been processed and all
// results are available.
function eachAsync(arr, func, cb) {
var doneCounter = 0,
results = [];
arr.forEach(function (item) {
func(item, function (res) {
doneCounter += 1;
results.push(res);
if (doneCounter === arr.length) {
cb(results);
}
});
});
}
eachAsync(items, someAsyncFunc, console.log);
Now, running node iterasync.js will wait for about three seconds and then print [ 'ONE', 'TWO', 'THREE' ]. This is a simple example, but it can be extended to handle many situations.
As correctly pointed out, you have to use setTimeout, for example:
each_async = function(ary, fn) {
var i = 0;
-function() {
fn(ary[i]);
if (++i < ary.length)
setTimeout(arguments.callee, 0)
}()
}
each_async([1,2,3,4], function(p) { console.log(p) })
The easiest way to handle async iteration of arrays (or any other iterable) is with the await operator (only in async functions) and for of loop.
(async function() {
for(let value of [ 0, 1 ]) {
value += await(Promise.resolve(1))
console.log(value)
}
})()
You can use a library to convert any functions you may need which accept callback to return promises.
In modern JavaScript there are interesting ways to extend an Array into an async itarable object.
Here I would like to demonstrate a skeleton of a totally new type AsyncArray which extends the Array type by inheriting it's goodness just to become an async iterable array.
This is only available in the modern engines. The code below uses the latest gimmicks like the private instance fields and for await...of.
If you are not familiar with them then I would advise you to have a look at the above linked topics in advance.
class AsyncArray extends Array {
#INDEX;
constructor(...ps){
super(...ps);
if (this.some(p => p.constructor !== Promise)) {
throw "All AsyncArray items must be a Promise";
}
}
[Symbol.asyncIterator]() {
this.#INDEX = 0;
return this;
};
next() {
return this.#INDEX < this.length ? this[this.#INDEX++].then(v => ({value: v, done: false}))
: Promise.resolve({done: true});
};
};
So an Async Iterable Array must contain promises. Only then it can return an iterator object which with every next() call returns a promise to eventually resolve into an object like {value : "whatever", done: false} or {done: true}. So basically everything returned is a promise here. The await abstraction unpacks the value within and gives it to us.
Now as I mentioned before, this AsyncArray type, since extended from Array, allows us to use those Array methods we are familiar with. That should simplify our job.
Let's see what happens;
class AsyncArray extends Array {
#INDEX;
constructor(...ps){
super(...ps);
if (this.some(p => p.constructor !== Promise)) {
throw "All AsyncArray items must be a Promise";
}
}
[Symbol.asyncIterator]() {
this.#INDEX = 0;
return this;
};
next() {
return this.#INDEX < this.length ? this[this.#INDEX++].then(v => ({value: v, done: false}))
: Promise.resolve({done: true});
};
};
var aa = AsyncArray.from({length:10}, (_,i) => new Promise(resolve => setTimeout(resolve,i*1000,[i,~~(Math.random()*100)])));
async function getAsycRandoms(){
for await (let random of aa){
console.log(`The Promise at index # ${random[0]} gets resolved with a random value of ${random[1]}`);
};
};
getAsycRandoms();
For modern Node.js:
To iterate through a collection truly asynchronously, you can try my tiny package with zero dependencies, compatible with ESM and CJS modules with .d.ts typings. Check the code it's really tiny.
https://www.npmjs.com/package/array-to-async-iterable
You can use it just like this:
for await(const el of new AsyncTimeIterator(arrayOfObjects)){
...
}
You can't just use for await of loop because of the JavaScript engines' microtasks and macrotasks nature.
In a brief, you won't get new HTTP requests and let other timers' callbacks to be executed with this code:
for await(const el of array){
...
}
You force V8 or the other engine to execute all the microtasks (your loop iteration) and when the loop completes you'll unblock the event loop and be ready to receive HTTP connections. So this code is completely useless.