
--- ## Asynchronous Programming in Python A high-level API for asynchronous operations was introduced in Python versions 3.4 to 3.7: - two new keywords: `async` and `await` (defining a _coroutine_) - standard module [`asyncio`](https://docs.python.org/3/library/asyncio.html) (API for running and managing _coroutines_) _Hello World_ example: ```python import asyncio async def main(): print('Hello ...') await asyncio.sleep(1) print('... World!') asyncio.run(main()) ``` --- ### Working with _coroutines_ 1. `asyncio.run()` – top-level interface for running an `async` function from a _synchronous context_ 2. _awaiting on coroutines_ – using the `await` keyword inside an `async` function - generally, there are 3 types of _awaitable_ objects: - __coroutines__ – Python functions defined as `async` - __Tasks__ – wrapper objects used to schedule _coroutines_ to run concurrently - __Futures__ – special low-level objects that represent the result of some operation that will complete in the future - `await` allows expressing which operations can be overlapped and processed concurrently --- #### Example with several _coroutines_ ```python import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at {time.strftime('%X')}") await say_after(1, 'hello') await say_after(2, 'world') print(f"finished at {time.strftime('%X')}") asyncio.run(main()) ``` Possible output (note the duration): ```txt started at 17:13:52 hello world finished at 17:13:55 ``` --- ### Concurrent Execution of _coroutines_ `asyncio.create_task()` – schedules a _coroutine_ for concurrent execution __Example:__ ```python async def main(): task1 = asyncio.create_task( say_after(1, 'hello') ) task2 = asyncio.create_task( say_after(2, 'world') ) print(f"started at {time.strftime('%X')}") await task1 await task2 print(f"finished at {time.strftime('%X')}") ``` Note that after this modification, the code will run 1 second faster (processing tasks is overlapped when calling `asyncio.sleep(delay)` in the `say_after()` function). __Note:__ It is still not _parallel processing_ – Python uses only one thread. --- ### Concurrent Execution of _coroutines_ Using `TaskGroup` The [`asyncio.TaskGroup`](https://docs.python.org/3/library/asyncio-task.html#asyncio.TaskGroup) class – a modern alternative to the `asyncio.create_task()` function __Example:__ ```python async def main(): async with asyncio.TaskGroup() as tg: task1 = tg.create_task( say_after(1, 'hello') ) task2 = tg.create_task( say_after(2, 'world') ) print(f"started at {time.strftime('%X')}") # The await is implicit when the context manager exits. print(f"finished at {time.strftime('%X')}") ``` --- ### Synchronization Between Tasks The `asyncio` module provides basic tools for synchronization, similar to the `threading` module: - `asyncio.Lock` - `asyncio.Event` - `asyncio.Condition` - `asyncio.Semaphore` - `asyncio.BoundedSemaphore` - `asyncio.Barrier` --- ### Event Loop - `asyncio.run()` starts a low-level _event loop_ where all asynchronous tasks are scheduled - _coroutines_ themselves are useless until they are bound to an _event loop_ - the default _event loop_ in the CPython interpreter uses __only one thread__ - _event loops_ are _pluggable_ – the default _event loop_ can be replaced with another implementation - e.g. [uvloop](https://uvloop.readthedocs.io/) fast implementation in the Cython language (performance is comparable to Go and other statically compiled languages) - it is possible to develop a multi-threaded _event loop_ (but there is still the GIL limitation – global interpreter lock) --- ### Using Asynchronous Programming in Applications Asynchronous API is useful especially for hiding latency when waiting for data (IO). - HTTP requests – client and server packages (`httpx`, `starlette`) - databases – `sqlalchemy`, ... - web frameworks – Django, FastAPI, ... <br> > __Demo with the `httpx` package:__ see [GitLab](https://gitlab.fjfi.cvut.cz/18inta-2024/inta) and [notes](https://jlk.fjfi.cvut.cz/md/aqQ_xRsjR9ejUNymi7-iNQ) --- ## Asynchronous Programming in JavaScript JavaScript also has asynchronous functions, similar to Python: - `async` and `await` keywords with similar semantics - `async` functions can be called directly – JavaScript does not have an alternative to `asyncio.run()` - `async` functions always return a `Promise` object, which has 3 possible states: _pending_, _fulfilled_, or _rejected_ - `await` on a `Promise` object either returns the result value or raises an exception - same behavior as in Python in terms of code structuring - [interface for concurrent processing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#promise_concurrency): `Promise.all`, `Promise.allSettled`, `Promise.any`, `Promise.race` methods > __Demo:__ [Async Functions in JavaScript](https://thecodebarbarian.com/async-functions-in-javascript.html) > __Documentation:__ [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function), [`Promise` object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) --- ## JavaScript Fetch API [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) allows asynchronous processing of HTTP requests in JavaScript. It is a modern alternative to the [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) interface. The basis is the `fetch` function: ```js async function f() { try { const url = "https://example.com"; await fetch(url, { mode: "cors" }); } catch(err) { alert(err); // Failed to fetch } } ``` __Note:__ Browsers block "unsafe" cross-origin requests (CORS) for security reasons. Details: [Fetch: Cross-Origin Requests](https://javascript.info/fetch-crossorigin).
TODO: advanced CORS - https://javascript.info/fetch-crossorigin - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS