At the lowest level, async I/O relies on the operating system telling you when something is ready instead of you blocking on it. Linux exposes epoll, BSD and macOS expose kqueue, and Windows exposes IOCP. Your program registers interest in many file descriptors or sockets, then makes one call that returns the subset that now have data ready to read or space ready to write.
On top of that sits an event loop. It is a single thread running a tight cycle: ask the OS which operations are ready, run the small piece of code (the callback) attached to each one, then loop again. Node.js, Python asyncio, and the Netty framework in Java all work this way. Because each callback runs to completion quickly and never blocks, one loop can service tens of thousands of connections.
Languages dress this up differently. JavaScript uses promises and async/await. Python uses coroutines with async/await driven by an event loop. Go uses goroutines and channels, where the runtime scheduler multiplexes thousands of lightweight goroutines onto a few OS threads and parks the ones that are waiting. In every case the trick is the same: when code would block, it yields control so the underlying scheduler can run something else.
A separate flavor of async is message-driven. Instead of a callback in the same process, the producer writes a message to a broker like Kafka or RabbitMQ and returns. A consumer picks it up seconds or minutes later. The two sides are fully decoupled in time, which is why this style is the backbone of event-driven architectures.