The author of this article is EPAM Senior Software Engineer Anjani Tharayil.
In this article
In the creation phase, memory is allocated by scanning through code for variables and functions.
The execution phase involves actual code execution.
Commands execute one by one; the next line follows only after the current one completes.
Code runs sequentially, with a sole main thread.
Long function calls (such as network requests) can block the main thread, causing unresponsiveness.
How to understand asynchronous programming
The event loop
Here's a high-level overview of how the event loop operates:
Execution stack: Functions execute one at a time, forming a stack.
Message queue: Asynchronous tasks (events, web APIs) await execution.
Event loop: Monitors stack, fetches tasks from queue one by one at a time when stack is empty.
Task execution: Tasks run in order, each completing before the next task begins.
Asynchronous: Non-blocking operations (callbacks, promises) allow scheduling
Completion: After async tasks finish, their results join the queue.
Event loop continues: Empties queue into stack, maintaining flow.
Responsive apps: Enables multitasking, responsiveness in applications.
Effectively managing asynchronous behavior is crucial to avoid memory overhead from multiple concurrent operations.
Control techniques for asynchronous operations:
Callbacks: Foundational for async handling, beware of callback hell.
Promises: Structured approach, chain operations, use .then() and .catch().
Async/Await: ES2017's concise syntax, async functions, await for Promises.
Generators and iterators: Pausable functions, async iteration via generators and iterators.
What are generators and iterators?
Generators represent a class of functions that can be halted and then restarted. They are declared using the `function*` syntax. Within a generator function, you can use the `yield` keyword to pause the function's execution and produce a value to the caller. The function can later be resumed from where it was paused.
Example of a generator function:
Generators are particularly useful for asynchronous operations because they allow you to pause execution while waiting for asynchronous tasks to complete and then resume execution when the tasks are done.
Here's a basic example of implementing an iterator:
Generators can be used to simplify the creation of iterators, especially when dealing with asynchronous operations. By using the `yield` keyword within a generator, you can easily produce values in a controlled and asynchronous manner.
Here's an example of a generator-based asynchronous iterator:
In this example, the `asyncNumberGenerator` produces values by awaiting an asynchronous function call.
Generators and iterators, especially when used together, provide a powerful toolset for managing asynchronous operations and creating more readable and maintainable asynchronous code.