javascriptmultithreadingasynchronous

Is JavaScript async and await the equivalent to multithreading?


Is using async and await the crude person's threads?

Many moons ago I learned how to do multithreaded Java code on Android. I recall I had to create threads, start threads, etc.

Now I'm learning JavaScript, and I just learned about async and await.

For example:

async function isThisLikeTwoThreads() {
 const a = slowFunction();
 const b = fastFunction();
 console.log(await a, await b);
}

This looks way simpler than what I used to do and is a lot more intuitive.

slowFunction() would start first, then fastFunction() would start, and console.log() would wait until both functions resolved before logging - and slowFunction() and fastFunction() are potentially running at the same time. I expect it's ultimatly on the browser whether or not thesea are seperate threads. But it looks like it walks and talks like crude multithreading. Is it?


Solution

  • Is using async and await the crude person's threads?

    No, not at all. It's just syntax sugar (really, really useful sugar) over using promises, which in turn is just a (really, really useful) formalized way to use callbacks. It's useful because you can wait asynchronously (without blocking the JavaScript main thread) for things that are, by nature, asynchronous (like HTTP requests).

    If you need to use threads, use web workers, Node.js worker threads, or whatever multi-threading your environment provides. Per specification (nowadays), only a single thread at a time is allowed to work within a given JavaScript "realm" (very loosely: the global environment your code is running in and its associated objects, etc.) and so only a single thread at a time has access to the variables and such within that realm, but threads can cooperate via messaging (including transferring objects between them without making copies) and shared memory.

    For example:

    async function isThisLikeTwoThreads() {
     const a = slowFunction();
     const b = fastFunction();
     console.log(await a, await b);
    }
    

    Here's what that code does when isThisLikeTwoThreads is called:

    1. slowFunction is called synchronously and its return value is assigned to a.
    2. fastFunction is called synchronously and its return value is assigned to b.
    3. When isThisLikeTwoThreads reaches await a, it wraps a in a promise (as though you did Promise.resolve(a)) and returns a new promise (not that same one). Let's call the promise wrapped around a "aPromise" and the promise returned by the function "functionPromise".
    4. Later, when aPromise settles, if it was rejected functionPromise is rejected with the same rejection reason and the following steps are skipped; if it was fulfilled, the next step is done
    5. The code in isThisLikeTwoThreads continues by wrapping b in a promise (bPromise) and waiting for that to settle
    6. When bPromise settles, if it was rejected functionPromise is rejected with the same rejection reason; if it was fulfilled, the code in isThisLikeTwoThreads continues by logging the fulfillment values of aPromise and bPromise and then fulfilling functionPromise with the value undefined

    All of the work above was done on the JavaScript thread where the call to isThisLikeTwoThreads was done, but it was spread out across multiple "jobs" (JavaScript terminology; the HTML spec calls them "tasks" and specifies a fair bit of detail for how they're handled on browsers). If slowFunction or fastFunction started an asynchronous process and returned a promise for that, that asynchronous process (for instance, an HTTP call the browser does) may have continued in parallel with the JavaScript thread while the JavaScript thread was doing other stuff or (if it was also JavaScript code on the main thread) may have competed for other work on the JavaScript thread (competed by adding jobs to the job queue and the thread processing them in a loop).

    But using promises doesn't add threading. :-)