Home > Software design >  Unawaited promises still executed
Unawaited promises still executed

Time:12-14

While debugging a set-up to control promise concurrency for some CPU intensive asynchronous tasks, I came across the following behavior that I can not understand.

My route to control the flow was to first create an array of Promises, and after that control the execution of these Promises by explicitly awaiting them. I produced the following snippet that shows the unexpected behavior:

import fs from 'node:fs';
import { setTimeout } from 'node:timers/promises';

async function innerAsync(n: number) {
  return fs.promises.readdir('.').then(value => {
    console.log(`Executed ${n}:`, value);
    return value;
  });
}

async function controllerAsync() {
  const deferredPromises = new Array<Promise<string[]>>();

  for (const n of [1, 2, 3]) {
    const defer = innerAsync(n);
    deferredPromises.push(defer);
  }

  const result = deferredPromises[0];
  return result;
}

const result = await controllerAsync();
console.log('Resolved:', result);

await setTimeout(1000);

This snippet produces the following result (assuming 2 files are in .):

Executed 1: [ 'test.txt', 'test2.txt' ]
Resolved: [ 'test.txt', 'test2.txt' ]
Executed 2: [ 'test.txt', 'test2.txt' ]
Executed 3: [ 'test.txt', 'test2.txt' ]

Observed

The promises defined at line 15 are all being executed, regardless if they are returned/awaited (2 and 3).

Question What causes the other, seemingly unused, promises to be executed? How can I make sure in this snippet only the first Promise is executed, where the others stay pending?


CodePudding user response:

What causes the other, seemingly unused, promises to be executed?

A promise is a tool used to watch something and provide an API to react to the something being complete.

Your innerAsync function calls fs.promises.readdir. Calling fs.promises.readdir makes something happen (reading a directory).

The then callback is called as a reaction when reading that directory is complete.

(It isn't clear why you marked innerAsync as async and then didn't use await inside it.)

If you don't use then or await, then that doesn't change the fact you have called fs.promises.readdir!

How can I make sure in this snippet only the first Promise is executed, where the others stay pending?

Focus on the function which does the thing, not the code which handles it being complete.


Or an analogy:

If you tell Alice to go to the shops and get milk, but don't tell Bob to put the milk in the fridge when Alice gets back, then you shouldn't be surprised that Alice has gone to the shops and come back with milk.

CodePudding user response:

Promises aren't "executed" at all. A promise is a way to observe an asynchronous process that is already in progress. By the time you have a promise, the process it's reporting the result of is already underway.¹ It's the call to fs.readdir that starts the process, not calling then on a promise. (And even if it were, you are calling then on the promise from fs.readdir right away, in your innerAsync function.)

If you want to wait to start the operations, wait to call the methods starting the operations and giving you the promises. I'd show an edited version of your code, but it's not clear to me what it's supposed to do, particularly since controllerAsync only looks at the first element of the array and doesn't wait for anything to complete before returning.


A couple of other notes:

  1. It's almost never useful to combine async/await with explicit calls to .then as in innerAsync. That function would be more clearly written as:

    async function innerAsync(n: number) {
        const value = await fs.promises.readdir(".");
        console.log(`Executed ${n}:`, value);
        return value;
    }
    

    From an order-of-operations perspective, that does exactly the same thing as the version with the explicit then.

  2. There's no purpose served by declaring controllerAsync as an async function, since it never uses await. The only reason for making a function async is so you can use await within it.


¹ This is true in the normal case (the vast majority of cases), including the case of the promises you get from Node.js' fs/promises methods. There are (or were), unfortunately, a couple of libraries that had functions that returned promises but didn't start the actual work until the promise's then method was called. But those were niche outliers and arguably violate the semantics of promises.

  • Related