JavaScript and Concurrency: Threads, Goroutines, and async/await Without Headaches

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

One Thread to Rule Them All: Myths and Truth About Concurrency in JavaScript

\n\n

Once upon a time, in the era of static sites and "Submit" buttons that blocked the entire interface, JavaScript lived happily in a single thread. It was enough: a request came in — it executed, a request left — the interface froze. But times have changed. Today, we expect instant reactions from web applications: scrolling shouldn't lag, animations should be smooth, and data loading should be invisible. And that's where concurrency comes into play.

\n\n

Many developers confuse concurrency with parallelism. In JavaScript, especially in the browser, we most often deal with the former. It's like a juggler: they don't hold all the balls in the air at the same time (that's parallelism), but they switch between them so deftly that an illusion of simultaneity is created. In this article, we'll break down three key tools for organizing concurrency in JS: async/await, goroutines (via generators and Web Workers), and classic threads. Let's go!

\n\n

Async/Await: Elegant Asynchronous Code

\n\n

async/await is syntactic sugar over Promises, introduced in ES2017. It doesn't make code multithreaded, but it makes it asynchronous. When you write await, JavaScript "sleeps" inside that function, freeing the thread for other tasks. As soon as the promise resolves, the function wakes up and continues execution. This is the perfect tool for I/O operations: API requests, file reading, timers.

\n\n

Simple Example: Loading Data from a Server

\n\n
// Function that returns a Promise\nfunction fetchUser(id) {\n    return new Promise((resolve) => {\n        setTimeout(() => resolve({ id, name: 'Alexey' }), 1000);\n    });\n}\n\n// Async function\nasync function loadUserProfile(userId) {\n    console.log('Starting load...');\n    const user = await fetchUser(userId);\n    console.log('User loaded:', user.name);\n    return user;\n}\n\nloadUserProfile(42);\nconsole.log('This code will execute immediately, without waiting for the load!');\n
\n\n

Notice: console.log('This code will execute immediately...') will run before console.log('User loaded'). The thread is not blocked — this is concurrency in action.

\n\n

Error Handling

\n\n
async function fetchWithRetry(url, retries = 3) {\n    for (let i = 0; i < retries; i++) {\n        try {\n            const response = await fetch(url);\n            if (!response.ok) throw new Error('HTTP error');\n            return await response.json();\n        } catch (error) {\n            if (i === retries - 1) throw error;\n            console.log(`Attempt ${i + 1} failed, trying again...`);\n        }\n    }\n}\n
\n\n

When to use: any operations with external resources (APIs, databases, file system in Node.js). async/await is your primary tool for asynchronous code.

\n\n

Goroutines in JavaScript: Myth or Reality?

\n\n

The term "goroutine" comes from Go and refers to a lightweight thread of execution. JavaScript doesn't have native goroutines, but it does have generators (Generator Functions) and Web Workers, which can simulate similar behavior. Generators allow you to pause and resume function execution, giving you control over the execution flow.

\n\n

Generators as a Foundation for Concurrency

\n\n
function* taskGenerator() {\n    console.log('Starting task 1');\n    yield 'Task 1 completed';\n    console.log('Starting task 2');\n    yield 'Task 2 completed';\n    console.log('Starting task 3');\n    yield 'Task 3 completed';\n}\n\nconst iterator = taskGenerator();\nconsole.log(iterator.next().value); // Executes task 1\n// At this point, you can do something else\nconsole.log(iterator.next().value); // Executes task 2\nconsol                

Blogs

Book Recommendations