async.mapLimit does not return a Promise

Created on 27 Aug 2019  ·  14Comments  ·  Source: caolan/async

What version of async are you using?
3.1.0

Which environment did the issue occur in (Node version/browser version)
node 12.9.1, npm 6.10.2, browser N/A

What did you do? Please include a minimal reproducable case illustrating issue.
Issue has a thread in stackoverflow
https://stackoverflow.com/questions/57622495/async-maplimit-with-promise/57659221#57659221

Basically, I have this code:

async = require('async');
let numPromise = async.mapLimit(['1','2','3','4','5'], 3, function(num, callback){
    setTimeout(function(){
        num = num * 2,
        console.log(num);
        callback(null, num);
    }, 
    2000);
})
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));

What did you expect to happen?
Execute without errors, 'numPromise' should contain a Promise. console should log '2,4,6,8,10' and 'success:2,4,6,8,10'

What was the actual result?
It throws an error: TypeError: Cannot read property 'then' of undefined

Note: When I use the 'promise-async' module instead of 'async' then this code works well. Documentation says that async.mapLimit (and others) return a Promise when no callback is supplied, but I get undefined. Couldn't find any working sample yet (please also see my suggestion on the 'need samples' issue).

bug

Most helpful comment

const async = require('async');
const delay = require('util').promisify(setTimeout);
const numPromise = async.mapLimit(['1','2','3','4','5'], 3, async num => delay(200).then(() => num*2))
// or const numPromise = async.mapLimit(['1','2','3','4','5'], 3, async num => {
//    await delay(200);
//    return num*2;
// })
numPromise.then(console.log)

All 14 comments

const async = require('async');
const delay = require('util').promisify(setTimeout);
const numPromise = async.mapLimit(['1','2','3','4','5'], 3, async num => delay(200).then(() => num*2))
// or const numPromise = async.mapLimit(['1','2','3','4','5'], 3, async num => {
//    await delay(200);
//    return num*2;
// })
numPromise.then(console.log)

Thx a lot, reasonable example. Unfortunately, gives me "SyntaxError: await is only valid in async function" (for 'await async.mapLimit')
Anything else I have to consider?

The error message says it all, await is only valid in an async function, until top-level-await proposal is implemented

Yip, was looking for a fully working example, as this is really the part I struggled with. Never mind though, got it up and running finally:

const myAsyncFunction = async function(){
    //const numPromise = await async.mapLimit(['1','2','3','4','5'], 3, async num => delay(200).then(() => num*2))
    const numPromise = await async.mapLimit(['1','2','3','4','5'], 3, async num => {
        await delay(2000);
        console.log(num*2);
        return num*2;
     })
    //numPromise.then(console.log)
    return numPromise;
}
myAsyncFunction()
.then((result) => console.log(result));

I'm not really sure yet what is different to earlier tries, but will validate and add some more conclusion on this.
In any case, my point is that this is all not fully intuitive, I believe, so examples like this can help!
And... I still don't fully understand why we need to wrap all the async/awaits around it, when async.mapLimit should just return a plain Promise by itself...

oops I forgot an await in my previous code, edited

Alright, now I fully confirm :-) So probably the main issue was that the iteratee function wasn't fully asynchronous?
Anyway, this works like a charm it seems and is probably a great example as well! Thx for staying on this with me!!

See https://github.com/caolan/async/issues/1673 for why

async.mapLimit(['1','2','3','4','5'], 3, async num => delay(200).then(() => num*2)) // works

async.mapLimit(['1','2','3','4','5'], 3, num => delay(200).then(() => num*2)) // doesn't work

Promise.all(['1','2','3','4','5'].map(num => delay(200).then(() => num*2))) // works (plain promises)

I see, you're completely with me, i.e. had the same issues.
I guess my main point really is the following - documentation just states "Returns: a Promise if no callback is passed" - so, when I have the callback variant, and then simply leave that out, how would I come to think that I need to add the 'async' keyword when it worked without that in the callback version. Plus, using the 'promise-async' module it works exactly like I expected.

In addition, I haven't yet grasped why the 'Promise.all' example above works... this is really confusing to me.

The iteratee function being async or using a callback shouldn't affect returning a promise if the final callback is omitted. This is a bug.

I'll bet you this is related to #1685

I'll bet you this is related to #1685

I definitely remember having had that issue before in some version of my code samples as well. I assumed obviously it was caused by some wrong coding. So, interesting to see that it appears in a different flavour as well.

I looked into this more.

mapLimit is working as intended. I would wager any issues people see here are due to the limitations of detecting promise-returning functions, or compilers (e.g. babel, typescript) not preserving async functions.

I looked into this more.

mapLimit is working as intended. I would wager any issues people see here are due to the limitations of detecting promise-returning functions, or compilers (e.g. babel, typescript) not preserving async functions.

What's the best way of handling such scenarios where async is not preserved on compilations?

Wrap the async function in asyncify. http://caolan.github.io/async/v3/global.html#AsyncFunction

Was this page helpful?
0 / 5 - 0 ratings