3

I have the follow code below:

var arr = [
    { condition: true, recursion: [], result: 'OLD_RESULT', recursiveArray: [{condition: true, result: 'OLD_RESULT'}] },
    {condition: false, result: 'OLD_RESULT'},
    { condition: true, recursion: [], result: 'OLD_RESULT', recursiveArray: [{condition: true, result: 'OLD_RESULT'}] },
    {condition: false, result: 'OLD_RESULT'},
];

var someAsyncCall = async () => {
    return 'NEW VALUE';
};

var asyncInMap = (arr) => {
    return arr.map(async (elem) => {
        const condition = elem.condition;
        if (condition) {
            const result = await someAsyncCall();
            elem.result = result;
        }
        if (elem.recursiveArray) {
            elem.recursion = asyncInMap(elem.recursiveArray);
        }
        console.log('SECOND:', elem);
        return elem;
    });
};

console.log('FIRST');
var promisesVal = asyncInMap(arr);
console.log('THIRD');
var completedVal = await Promise.all(promisesVal);
console.log('FOURTH', completedVal);

My question is in the order of the console.log's I am getting:

FIRST
SECOND: {condition: false, result: "OLD_RESULT"}
SECOND: {condition: false, result: "OLD_RESULT"}
THIRD
SECOND: {condition: true, recursion: Array(1), result: "NEW VALUE", recursiveArray: Array(1)}
SECOND: {condition: true, recursion: Array(1), result: "NEW VALUE", recursiveArray: Array(1)}
SECOND: {condition: true, result: "NEW VALUE"}
SECOND: {condition: true, result: "NEW VALUE"}
FOURTH (4) [{…}, {…}, {…}, {…}]

Why is the Third log being printed before all the Second console.log are done? Is this because I am using recursion inside of the asyncInMap function along with async/await? Ideally the asyncInMap should be completed (with all the Second logs printed first) and then only the Third log should be printed correct?

Bobby
  • 537
  • 1
  • 5
  • 14
  • It runs in StackOverflow if you don't check the "Use BabelJS" option. That's not needed since most browsers support ES6, although nothing gets logged. – Barmar Feb 02 '21 at 23:53
  • @Barmar In safari I believe I needed to check that. But when you paste it in a supported browser with ES6, do you happen to know why the logs come out in that order? – Bobby Feb 02 '21 at 23:56
  • 1
    This might be a duplicate of https://stackoverflow.com/questions/63929347/why-is-my-function-returning-promise-pending?noredirect=1#comment113047163_63929347 – Barmar Feb 02 '21 at 23:56

1 Answers1

3

that is the feature of async await code in js. THIRD will be called earlier than any asynchronious operation inside of that call, because event loop works like that in js. But there is a mistake in your code that can make FOURTH be logged earlier than all of the SECONDs are complete, because you are not waiting till recursive SECONDs are completed anywhere. To fix that you could add await here

elem.recursion = await Promise.all(asyncInMap(elem.recursiveArray));

also if you would like to persist the order of execution, you could do simple iterations and do all the awaiting logic at the function top level

var asyncInMap = async (arr) => {
     for(const elem of arr) {
       if(elem.condition) {
         elem.result = await someAsyncCall();
       }
       if(elem.recursiveArray) elem.recursion = await asyncInMap(elem.recursiveArray);
       console.log('SECOND:', elem);
     }
     return arr;
};



console.log('FIRST');
var promisesVal = asyncInMap(arr);
console.log('THIRD', 'calls can still be in progress here');
var completedVal = await promisesVal;
console.log('FOURTH', 'everything is completed here', completedVal);

note, that in the last example there will be no parallel operation executing at the same time. they will be "async called" one by one in a predefined order

Andrei
  • 10,117
  • 13
  • 21
  • This line: `elem.recursion = await Promise.all(asyncInMap(elem.recursiveArray));` just made me realize what I was doing wrong. Wasn't waiting for those promises from the recursive call to be awaited. Thank you for the example and taking the time to explain! – Bobby Feb 03 '21 at 00:24
  • After adding this code: `elem.recursion = await Promise.all(asyncInMap(elem.recursiveArray));` the `Third` shouldn't print before all the `Second` logs are done correct? Since now we're awaiting for the promises from the recursive call. – Bobby Feb 03 '21 at 01:06
  • you should await before printing third. otherwise printing third is a part od a synchronious callstack, and it is not possible to make synchrobious code be executed after the asynchronious one – Andrei Feb 03 '21 at 01:11
  • Is this line here: `elem.result = await someAsyncCall();` fine? Since I am assigning the result of the `someAsyncCall()` to `elem.result`. Since someAsyncCall() is async and map runs synchronously, the `elem.result` will be updated correctly and not be undefined correct? – Bobby Feb 10 '21 at 17:22
  • `await` is a keyword that "unwraps" promise. it postpones the line evaluation untill the promise result comes, and then the result of a promise will be assigned – Andrei Feb 10 '21 at 19:02
  • Oh ok, since `map` function runs synchronously and it doesn't block at the await, how does it work then? It returns a promise of what exactly? – Bobby Feb 11 '21 at 00:17
  • `asyncInMap` returns an array of promises that we can combine in a single promise with `Promise.all(...)` and then await this promise – Andrei Feb 11 '21 at 09:49
  • Yeah that part I got. Like in general when you use `async await` inside of a `map` function it returns an array of promises. But in my case I am doing `return elem` which is an element in the array and not a promise. So that's where I am confused. – Bobby Feb 17 '21 at 22:30
  • 1
    if you mark function `async` then it automatically wraps the returning value in `Promise`, even if the function body is whole sync. there is no possilibility in js to call async code from some piece of code and return flow control in that sync code. just the async ways – Andrei Feb 17 '21 at 23:29
  • So when it reaches this line: `const result = await someAsyncCall();` does it automatically return a promise? Since it won't stop execution there and the map callback function will continue executing? – Bobby Feb 18 '21 at 02:17
  • 1
    `await Promise.all(arr.map(asyncFn))` - here async calls will be "parallel", because .map will just create array of promises, without w8ing for each of them. `for(let item of arr) await asyncFn(item)` - here it will stop the execution on every item, and will w8t untill promise for item is resolved, – Andrei Feb 18 '21 at 10:16