0

I'm new to async-wait programming in typescript, so I wrote an experiment:

import { AssertionError } from 'assert'

async function handle(numbers: Array<number>) {
  numbers.forEach(compute)
}

async function compute(n: number) {
  let primesFound = 0;
  for (let p = 2; primesFound < n; p++) {
    if (isPrime(p)) {
      console.log(`found prime: ${p}`)
      primesFound++;
    }
  }
  throw new AssertionError();
}

function isPrime(p: number) {
  for (let i=2;i<p;i++) {
    for (let j=2;j<p;j++) {
      if (i*j === p) {
        return false;
      }
    }
  }
  return true;
}

async function main() {
  await handle([1000, 1000, 5])
}

main()

I expected the three computations to happen in parallel, with the shortest of them hitting the assertion failure first - that did not happen. The three computations happened sequentially, and only after they all finished the assertion fired:

... // omitted for brevity
found prime: 7907
found prime: 7919
found prime: 2
found prime: 3
found prime: 5
found prime: 7
found prime: 11
node:internal/errors:484
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "options" argument must be of type object. Received undefined
    at new AssertionError (node:internal/assert/assertion_error:327:5)
OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87

1 Answers1

2

Using async for a function that never uses await is a code smell, because that means the whole async function body will execute synchronously, and the calling code can only inspect the returned promise when that is done, which will be in resolved state.

To get more asynchrony you should either:

  1. Introduce await inside the long-running function, so that its execution is broken into pieces that each get executed as an asynchronous job (except the first chunk which remains synchronous).

  2. Use web workers.

Here is how the first idea could work out:

function handle(numbers) {
  // Promise.all is interesting for the caller to know when all is done
  return Promise.all(numbers.map(compute));
}

async function compute(n) {
  let primesFound = 0;
  for (let p = 2; primesFound < n; p++) {
    if (await isPrime(p)) {  // isPrime is now async
      console.log(`found prime: ${p}`)
      primesFound++;
    }
  }
}

async function isPrime(p) {
  let count = 0;
  for (let i=2;i<p;i++) {
    for (let j=2;j<p;j++) {
      if (count++ % 100 == 0) await null; // split work into asynchronous jobs
      if (i*j === p) {
        return false;
      }
    }
  }
  return true;
}

async function main() {
  await handle([20, 20, 5]);
  console.log("All done.");
}

main();

Note that console.log output is not guaranteed to display synchronously, as the host will typically wait for the ongoing JavaScript task to finish before spending time on UI-related jobs, and the above code is executed as one task (with several asynchronous microtasks executing within that same task).

trincot
  • 317,000
  • 35
  • 244
  • 286
  • The OP wants [parallel execution](https://stackoverflow.com/questions/75521426/async-await-weird-behavior-typescript/75521520?noredirect=1#comment133244607_75521426) – Liam Feb 21 '23 at 16:00
  • 1
    Sure, but that term is open for interpretation. Literally speaking there is no parallel execution when a system has only one CPU which can only execute one instruction at a time. This answer just proposes some round robin execution of pieces of work using the main thread, which produces a result in line with their expectation that the "shortest" should finish first. If OP expected only a *multithreading* solution, they should have specified this in their question, and it is not my interpretation of it. – trincot Feb 21 '23 at 16:08
  • that's useful thanks ! the location of the `await null` is the point of the context-switch, right? – OrenIshShalom Feb 21 '23 at 17:49
  • Yes that suspends the function. It gets resumed by a job in the promise job queue when the callstack is empty. – trincot Feb 21 '23 at 18:09