Emilio Grisolía Emilio Grisolía - 3 days ago 5
Javascript Question

Is this really an async "for-like" loop?

I was trying to understand a little bit more about promises and async programming. I was trying to make an async for loop (yeah, I know there are lots of libs to accomplish this, but they don't teach me how things work) using promises.

Let's say I want to iterate an array, and apply a function to one element "per tick" while I do some other stuff. So I made this "async for-loop-like" function:

function asyncFor_(elements, worker, index) {
return new Promise((resolve, reject) => {
process.nextTick(()=>{
if (index < elements.length) {
try {
worker(elements[index])
resolve(asyncFor_(elements, worker, index+1))
} catch(e) {
reject()
}
} else {
resolve()
}
})
})
}


And test it with this:

function logEnd() {
console.log('End')
}
function logErr(e) {
console.log(e) //Received
console.log('I dont like 3. GTFO.')
}
function logItem(item) {
if (item === 3) {
throw Error('3? GTFO.')
}
console.log(item)
}
console.log('Begin')
asyncFor_([1,2,3,4,5], logItem, 0)
.then(logEnd)
.catch(logErr)
asyncFor_([6,7,8,9], logItem, 0)
.then(logEnd)
.catch(logErr)
console.log('Displayed after begin and before the rest')


The output is:

Begin
1
6
2
7
8
I don't like 3. GTFO
9
End (End from the second asyncFor_ call)


I think this is working fine. But at the same time I'm having doubts. Maybe I am misinterpreting the results. Did I miss something? Is this "asynchrony" an illusion or is it really async?

Answer

Yes, it is fine, and yes, it is really asynchronous (as also evidenced by your output log from the two concurrent loops).

However it looks a bit like the Promise constructor antipattern, and by avoiding that you can greatly simplify your code:

function nextTick() {
    return new Promise(resolve => {
        process.nextTick(resolve);
    });
}

function asyncFor_(elements, worker, index) {
    return nextTick().then(() => {
        if (index < elements.length) {
            worker(elements[index]);
            return asyncFor_(elements, worker, index+1);
        }
    });
}

Putting your code in then callbacks, you've got the try-catch for free. Always promisify at the lowest possible level! :-)

Comments