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)
11
  • 1
    Marking a function as async doesn't magically make it asynchronous. Most of the js runs in one thread anyway, you can't do things in parallel Commented Feb 21, 2023 at 13:53
  • 2
    Also all those async methods are blocking. Async is not parallel, they're different things. You can only async on an actual async none blocking call Commented Feb 21, 2023 at 13:54
  • 2
    You can use workers for that Commented Feb 21, 2023 at 13:55
  • 2
    Web browsers have facilities (web workers) that can allow computation on other threads. Actual parallelism relies on the computing platform and operating system of course. Commented Feb 21, 2023 at 13:56
  • 3
    @pacifica94 Promise.all() has nothing to do with this question. All the code is synchronous. Commented Feb 21, 2023 at 13:59

1 Answer 1

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).

Sign up to request clarification or add additional context in comments.

4 Comments

The OP wants parallel execution
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.
that's useful thanks ! the location of the await null is the point of the context-switch, right?
Yes that suspends the function. It gets resumed by a job in the promise job queue when the callstack is empty.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.