Await

Await Expression

The following new await expression is used to obtain a result of coroutine execution:

async def read_data(db):
    data = await db.fetch('SELECT ...')
    ...

await, similarly to yield from, suspends execution of read_data coroutine until db.fetch awaitable completes and returns the result data.

It uses the yield from implementation with an extra step of validating its argument. await only accepts an awaitable, which can be one of:

  • A native coroutine object returned from a native coroutine function.

  • A generator-based coroutine object returned from a function decorated with types.coroutine().

  • An object with an __await__ method returning an iterator.


    Any yield from chain of calls ends with a yield. This is a fundamental mechanism of how Futures are implemented. Since, internally, coroutines are a special kind of generators, every await is suspended by a yield somewhere down the chain of await calls (please refer to PEP 3156 for a detailed explanation).

    To enable this behavior for coroutines, a new magic method called __await__ is added. In asyncio, for instance, to enable Future objects in await statements, the only change is to add __await__ = __iter__ line to asyncio.Future class.

    Objects with __await__ method are called Future-like objects in the rest of this PEP.

    It is a TypeError if __await__ returns anything but an iterator.

  • Objects defined with CPython C API with a tp_as_async.am_await function, returning an iterator (similar to __await__ method).

It is a SyntaxError to use await outside of an async def function (like it is a SyntaxError to use yield outside of def function).

It is a TypeError to pass anything other than an awaitable object to an await expression.

await keyword is defined as follows:

power ::=  await 
await ::=   primary

where «primary» represents the most tightly bound operations of the language. Its syntax is:

primary ::=  atom | attributeref | subscription | slicing | call

See Python Documentation and section of this proposal for details.

The key await difference from yield and yield from operators is that await expressions do not require parentheses around them most of the times.

Also, yield from allows any expression as its argument, including expressions like yield from a() + b(), that would be parsed as yield from (a() + b()), which is almost always a bug. In general, the result of any arithmetic operation is not an awaitable object. To avoid this kind of mistakes, it was decided to make await precedence lower than [], (), and ., but higher than ** operators.

Operator Description
yield x, yield from x Yield expression
lambda Lambda expression
ifelse Conditional expression
or Boolean OR
and Boolean AND
not x Boolean NOT
in, not in, is, is not, <, <=, >, >=, !=, == Comparisons, including membership tests and identity tests
| Bitwise OR
^ Bitwise XOR
& Bitwise AND
<<, >> Shifts
+, - Addition and subtraction
*, @, , //, % Multiplication, matrix multiplication, division, remainder
+x, -x, ~x Positive, negative, bitwise NOT
** Exponentiation
await x Await expression
x, x, x(arguments...), x.attribute Subscription, slicing, call, attribute reference
(expressions...), , {key: value...}, {expressions...} Binding or tuple display, list display, dictionary display, set display

PEP 3152

PEP 3152 by Gregory Ewing proposes a different mechanism for coroutines (called «cofunctions»). Some key points:

  1. A new keyword codef to declare a cofunction. Cofunction is always a generator, even if there is no cocall expressions inside it. Maps to async def in this proposal.

  2. A new keyword cocall to call a cofunction. Can only be used inside a cofunction. Maps to await in this proposal (with some differences, see below).

  3. It is not possible to call a cofunction without a cocall keyword.

  4. cocall grammatically requires parentheses after it:

    atom: cocall | <existing alternatives for atom>
    cocall: 'cocall' atom cotrailer* '('  ')'
    cotrailer: '' | '.' NAME
    
  5. cocall f(*args, **kwds) is semantically equivalent to yield from f.__cocall__(*args, **kwds).

Differences from this proposal:

Async функции

Добавление async-функций в ES2017 (ES8) сделало работу с промисами легче.

Важно отметить, что async-функции работают поверх промисов. Эти функции не являются принципиально другими концепциями. Async-функции были задуманы как альтернатива коду, использующему промисы. Используя конструкцию async/await, можно полностью избежать использование цепочек промисов. С помощью async-функций возможно организовать работу с асинхронным кодом в синхронном стиле.

Как видите, знание промисов всё же необходимо для понимания работы async/await.

Синтаксис

Синтаксис состоит из двух ключевых слов: и . Первое делает функцию асинхронной. Именно в таких функциях разрешается использование . Использование в любом другом случае вызовет ошибку.

Обратите внимание, что вставляется в начале объявления функции, а в случае стрелочной функции — между знаком и скобками. Async-функции могут быть помещены в объект в качестве методов или же просто использоваться в объявлении класса

Async-функции могут быть помещены в объект в качестве методов или же просто использоваться в объявлении класса.

Примечание Конструкторы класса и геттеры/сеттеры не могут быть асинхронными.

Async-функции всегда возвращают промисы

Функция возвращает строку . Т. к. это асинхронная функция, значение строки обёртывается в промис (с помощью конструктора).

Код выше можно переписать и без использования :

В таком случае, вместо , код вручную возвращает промис.

Тело асинхронной функции всегда обёртывается в новый промис

Если возвращаемое значение является примитивом, async-функция возвращает это значение, обёрнутое в промис. Но если возвращаемое значение и есть объект промиса, его решение возвращается в новом промисе.

Что происходит, когда внутри асинхронной функции возникает какая-нибудь ошибка?

Если ошибка не будет обработана, вернёт промис с реджектом. В таком случае вместо вернётся , содержащий ошибку.

Суть async-функций в том, что что бы вы не возвращали, на выходе вы всегда будете получать промис.

Асинхронные функции приостанавливаются при каждом await выражении

сказывается на выражениях. Если выражение является промисом, то async-функция будет приостановлена до тех пор, пока промис не выполнится. Если же выражение не является промисом, то оно конвертируется в промис через и потом завершается.

Как работает функция?

  1. После вызова функции первая строка конвертируется из в .
  2. После использования , выполнение функции приостанавливается, пока не получит своё значение (в данном случае это 9).
  3. приостанавливает выполнение функции, пока не завершится сама (после 1 секунды). Это, фактически, можно назвать остановкой функции на 1 секунду.
  4. Также через возвращает случайное значение, которое присваивается переменной .
  5. Случай с переменной идентичен случаю переменной . После этого опять происходит пауза на 1 секунду, но теперь ничего не возвращает, т. к. этого не требуется.
  6. Под конец эти значения считаются по формуле . Результат обёртывается в промис с помощью и возвращается функцией.

Примечание Если такие паузы напоминают вам генераторы в ES6, то на это есть свои причины.

Asynchronous Iterators and «async for»

An asynchronous iterable is able to call asynchronous code in its iter implementation, and asynchronous iterator can call asynchronous code in its next method. To support asynchronous iteration:

  1. An object must implement an __aiter__ method (or, if defined with CPython C API, tp_as_async.am_aiter slot) returning an asynchronous iterator object.
  2. An asynchronous iterator object must implement an __anext__ method (or, if defined with CPython C API, tp_as_async.am_anext slot) returning an awaitable.
  3. To stop iteration __anext__ must raise a StopAsyncIteration exception.

An example of asynchronous iterable:

class AsyncIterable:
    def __aiter__(self):
        return self

    async def __anext__(self):
        data = await self.fetch_data()
        if data:
            return data
        else:
            raise StopAsyncIteration

    async def fetch_data(self):
        ...

A new statement for iterating through asynchronous iterators is proposed:

async for TARGET in ITER:
    BLOCK
else:
    BLOCK2

which is semantically equivalent to:

iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True
while running:
    try:
        TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
        running = False
    else:
        BLOCK
else:
    BLOCK2

It is a TypeError to pass a regular iterable without __aiter__ method to async for. It is a SyntaxError to use async for outside of an async def function.

As for with regular for statement, async for has an optional else clause.

With asynchronous iteration protocol it is possible to asynchronously buffer data during iteration:

async for data in cursor:
    ...

Where cursor is an asynchronous iterator that prefetches N rows of data from a database after every N iterations.

The following code illustrates new asynchronous iteration protocol:

class Cursor:
    def __init__(self):
        self.buffer = collections.deque()

    async def _prefetch(self):
        ...

    def __aiter__(self):
        return self

    async def __anext__(self):
        if not self.buffer:
            self.buffer = await self._prefetch()
            if not self.buffer:
                raise StopAsyncIteration
        return self.buffer.popleft()

then the Cursor class can be used as follows:

async for row in Cursor():
    print(row)

which would be equivalent to the following code:

i = Cursor().__aiter__()
while True:
    try:
        row = await i.__anext__()
    except StopAsyncIteration:
        break
    else:
        print(row)

The following is a utility class that transforms a regular iterable to an asynchronous one. While this is not a very useful thing to do, the code illustrates the relationship between regular and asynchronous iterators.

class AsyncIteratorWrapper:
    def __init__(self, obj):
        self._it = iter(obj)

    def __aiter__(self):
        return self

    async def __anext__(self):
        try:
            value = next(self._it)
        except StopIteration:
            raise StopAsyncIteration
        return value

async for letter in AsyncIteratorWrapper("abc"):
    print(letter)

JavaScript embedder API#

Library developers that handle their own asynchronous resources performing tasks like I/O, connection pooling, or managing callback queues may use the JavaScript API so that all the appropriate callbacks are called.

Class:

The class is designed to be extended by the embedder’s async resources. Using this, users can easily trigger the lifetime events of their own resources.

The hook will trigger when an is instantiated.

The following is an overview of the API.

  • The type of async event.
  • <Object>

    • The ID of the execution context that created this async event. Default: .
    • If set to , disables when the object is garbage collected. This usually does not need to be set (even if is called manually), unless the resource’s is retrieved and the sensitive API’s is called with it. When set to , the call on garbage collection will only take place if there is at least one active hook. Default: .

Example usage:

Static method:

Added in: v14.8.0

  • <Function> The function to bind to the current execution context.
  • An optional name to associate with the underlying .

Binds the given function to the current execution context.

The returned function will have an property referencing the to which the function is bound.

Added in: v14.8.0

fn The function to bind to the current AsyncResource.

Binds the given function to execute to this ‘s scope.

The returned function will have an property referencing the to which the function is bound.

Added in: v9.6.0

  • <Function> The function to call in the execution context of this async resource.
  • The receiver to be used for the function call.
  • Optional arguments to pass to the function.

Call the provided function with the provided arguments in the execution context of the async resource. This will establish the context, trigger the AsyncHooks before callbacks, call the function, trigger the AsyncHooks after callbacks, and then restore the original execution context.

Returns: A reference to asyncResource.

Call all hooks. This should only ever be called once. An error will be thrown if it is called more than once. This must be manually called. If the resource is left to be collected by the GC then the hooks will never be called.

Returns: The unique asyncId assigned to the resource.

Returns: The same triggerAsyncId that is passed to the AsyncResource constructor.

Using for a thread pool

The following example shows how to use the class to properly provide async tracking for a pool. Other resource pools, such as database connection pools, can follow a similar model.

Assuming that the task is adding two numbers, using a file named with the following content:

a Worker pool around it could use the following structure:

Without the explicit tracking added by the objects, it would appear that the callbacks are associated with the individual objects. However, the creation of the s is not associated with the creation of the tasks and does not provide information about when tasks were scheduled.

This pool could be used as follows:

Integrating with

Event listeners triggered by an may be run in a different execution context than the one that was active when was called.

The following example shows how to use the class to properly associate an event listener with the correct execution context. The same approach can be applied to a or a similar event-driven class.

Ловушки Async/await

Итак, какие ошибки разработчик может допустить при использовании ? Вот некоторые наиболее общие.

Слишком много последовательностей

Благодаря может создастся впечатление, что код будет исполняться последовательно, имейте в виду, что он все еще асинхронный, и нужно быть осторожными, чтобы не нагромождать слишком много последовательностей.

async getBooksAndAuthor(authorId) {
  const books = await bookModel.fetchAll();
  const author = await authorModel.fetch(authorId);
  return {
    author,
    books: books.filter(book => book.authorId === authorId),
  };
}

Этот код выглядит правильно с точки зрения логики. Однако работать он будет некорректно.

  1.  будет ждать ответа от функции .
  2. Затем будет вызван метод .

Обратите внимание, что  не зависит от результата , и на самом деле их можно вызывать параллельно! Однако, используя , эти два вызова становятся последовательными, и общее время выполнения будет намного больше, чем при параллельном варианте выполнения функций. Вот правильный способ:

Вот правильный способ:

async getBooksAndAuthor(authorId) {
  const bookPromise = bookModel.fetchAll();
  const authorPromise = authorModel.fetch(authorId);
  const book = await bookPromise;
  const author = await authorPromise;
  return {
    author,
    books: books.filter(book => book.authorId === authorId),
  };
}

Или того хуже, вдруг вам захочется получить список книг одна за другой, тогда вам придется прибегнуть к промисам:

async getAuthors(authorIds) {
  // WRONG, this will cause sequential calls
  // const authors = _.map(
  //   authorIds,
  //   id => await authorModel.fetch(id));
  // CORRECT
  const promises = _.map(authorIds, id => authorModel.fetch(id));
  const authors = await Promise.all(promises);
}

Короче говоря, вам все равно нужно предусмотреть асинхронный порядок исполнения кода, а затем попытаться написать код последовательно с . В сложных случаях лучшим вариантом будет использовать промисы напрямую.

Error handling

If a promise resolves normally, then returns the result. But in the case of a rejection, it throws the error, just as if there were a statement at that line.

This code:

…is the same as this:

In real situations, the promise may take some time before it rejects. In that case there will be a delay before throws an error.

We can catch that error using , the same way as a regular :

In the case of an error, the control jumps to the block. We can also wrap multiple lines:

If we don’t have , then the promise generated by the call of the async function becomes rejected. We can append to handle it:

If we forget to add there, then we get an unhandled promise error (viewable in the console). We can catch such errors using a global event handler as described in the chapter Error handling with promises.

and

When we use , we rarely need , because handles the waiting for us. And we can use a regular instead of . That’s usually (but not always) more convenient.

But at the top level of the code, when we’re outside any function, we’re syntactically unable to use , so it’s a normal practice to add to handle the final result or falling-through error, like in the line of the example above.

works well with

When we need to wait for multiple promises, we can wrap them in and then :

In the case of an error, it propagates as usual, from the failed promise to , and then becomes an exception that we can catch using around the call.

Истоки асинхронности. Устройство стандартных асинхронных методов

Итак, рассмотрим несколько корней асинхронности.

  1. Task.Run, new Task(..).Start(), Factory.StartNew и ему подобные. Самый простой способ начать асинхронное выполнение. Данные способы просто создают новый объект задачи, передавая в качестве одного из параметров делегат. Задача передается планировщику, который дает ее на выполнение одному из потоков пула. Возвращается готовая задача, которую можно ожидать. Как правило, такой подход используется для начала вычислений (CPU-bound) в отдельном потоке
  2. TaskCompletionSource. Вспомогательный класс, который помогает контролировать объект задачи. Создан для тех, кто не может выделить делегат под выполнение и использует более сложные механизмы контроля завершенности. Имеет очень простое API — SetResult, SetError и тд, которые соответствующим образом обновляют задачу. Данная задача доступна через свойство Task. Возможно, внутри вы будете создавать потоки, иметь сложную логику по их взаимодействию или завершение по событию. Чуть больше деталей об этом классе будет в последнем разделе

Class: AsyncLocalStorage#


Added in: v13.10.0

This class is used to create asynchronous state within callbacks and promise chains. It allows storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.

The following example uses to build a simple logger that assigns IDs to incoming HTTP requests and includes them in messages logged within each request.

When having multiple instances of , they are independent from each other. It is safe to instantiate this class multiple times.

Added in: v13.10.0

Creates a new instance of . Store is only provided within a method call.

Added in: v13.10.0

This method disables the instance of . All subsequent calls to will return until is called again.

When calling , all current contexts linked to the instance will be exited.

Calling is required before the can be garbage collected. This does not apply to stores provided by the , as those objects are garbage collected along with the corresponding async resources.

This method is to be used when the is not in use anymore in the current process.

Added in: v13.10.0

Returns:

This method returns the current store. If this method is called outside of an asynchronous context initialized by calling , it will return .

Added in: v13.11.0

store

Calling will transition into the context for the remainder of the current synchronous execution and will persist through any following asynchronous calls.

Example:

This transition will continue for the entire synchronous execution. This means that if, for example, the context is entered within an event handler subsequent event handlers will also run within that context unless specifically bound to another context with an .

Added in: v13.10.0

  • <Function>

This methods runs a function synchronously within a context and return its return value. The store is not accessible outside of the callback function or the asynchronous operations created within the callback.

Optionally, arguments can be passed to the function. They will be passed to the callback function.

If the callback function throws an error, it will be thrown by too. The stacktrace will not be impacted by this call and the context will be exited.

Example:

Added in: v13.10.0

  • <Function>

This methods runs a function synchronously outside of a context and return its return value. The store is not accessible within the callback function or the asynchronous operations created within the callback.

Optionally, arguments can be passed to the function. They will be passed to the callback function.

If the callback function throws an error, it will be thrown by too. The stacktrace will not be impacted by this call and the context will be re-entered.

Example:

Usage with

If, within an async function, only one call is to run within a context, the following pattern should be used:

In this example, the store is only available in the callback function and the functions called by . Outside of , calling will return .

Troubleshooting

In most cases your application or library code should have no issues with . But in rare cases you may face situations when the current store is lost in one of asynchronous operations. Then you should consider the following options.

If your code is callback-based, it is enough to promisify it with , so it starts working with native promises.

If you need to keep using callback-based API, or your code assumes a custom thenable implementation, you should use class to associate the asynchronous operation with the correct execution context.

defer

Атрибут сообщает браузеру, что он должен продолжать обрабатывать страницу и загружать скрипт в фоновом режиме, а затем запустить этот скрипт, когда он загрузится.

Вот тот же пример, что и выше, но с :

  • Скрипты с никогда не блокируют страницу.
  • Скрипты с всегда выполняются, когда дерево DOM готово, но до события .

Следующий пример это показывает:

  1. Содержимое страницы отобразится мгновенно.
  2. Событие подождёт отложенный скрипт. Оно будет сгенерировано, только когда скрипт будет загружен и выполнен.

Отложенные с помощью скрипты сохраняют порядок относительно друг друга, как и обычные скрипты.

Поэтому, если сначала загружается большой скрипт, а затем меньшего размера, то последний будет ждать.

Маленький скрипт загрузится первым, но выполнится вторым

Браузеры сканируют страницу на предмет скриптов и загружают их параллельно в целях увеличения производительности. Поэтому и в примере выше оба скрипта скачиваются параллельно. скорее всего загрузится первым.

Но спецификация требует последовательного выполнения скриптов согласно порядку в документе, поэтому он подождёт выполнения .

Атрибут предназначен только для внешних скриптов

Атрибут будет проигнорирован, если в теге нет .

Summary

The keyword before a function has two effects:

  1. Makes it always return a promise.
  2. Allows to be used in it.

The keyword before a promise makes JavaScript wait until that promise settles, and then:

  1. If it’s an error, the exception is generated — same as if were called at that very place.
  2. Otherwise, it returns the result.

Together they provide a great framework to write asynchronous code that is easy to both read and write.

With we rarely need to write , but we still shouldn’t forget that they are based on promises, because sometimes (e.g. in the outermost scope) we have to use these methods. Also is nice when we are waiting for many tasks simultaneously.

Пример из жизни

  1. Нарежьте морковь.
  2. Нарежьте лук.
  3. Добавьте воду в кастрюлю, включите плиту и подождите, пока она закипит.
  4. Добавьте морковь в кастрюлю и оставьте на 5 минут.
  5. Добавьте лук в кастрюлю и варите еще 10 минут.
  • Шаги 3, 4 и 5 фактически не требуют от вас как от шеф-повара ничего делать, кроме как наблюдать за процессом и следить за временем.
  • Шаги 1 и 2 требуют от вас, чтобы вы активно что-то делали.
  1. Начните кипятить кастрюлю с водой.
  2. Пока ждете, когда кастрюля закипит, начните резать морковь.
  3. К тому времени, когда вы закончите измельчать морковь, вода должна закипеть, поэтому добавьте морковь.
  4. Пока морковь готовится в кастрюле, нарежьте лук.
  5. Добавьте лук и готовьте еще 10 минут.

вам никогда не захочется сидеть сложа руки, просто ожидая чего-то, в то время как вы могли бы потратить свое время на какие-нибудь другие полезные дела.ожиданиевсегда асинхронное программирование

Обработка ошибок

При использовании промисов функция async может возвращать два значения: разрешенное значение и отклоненное значение. И мы можем использовать  для обычного случая и  для случая с ошибкой. Однако обработвать ошибки в случае с  — дело сложное.

try…catch

Самый стандартный способ, его же я обычно рекомендую — использовать конструкцию . При ожидании вызова, то есть в случае с любое отклоненное значение будет выброшено как исключение. Вот пример:

class BookModel {
  fetchAll() {
    return new Promise((resolve, reject) => {
      window.setTimeout(() => { reject({'error': 400}) }, 1000);
    });
  }
}
// async/await
async getBooksByAuthorWithAwait(authorId) {
try {
  const books = await bookModel.fetchAll();
} catch (error) {
  console.log(error);    // { "error": 400 }
}

Error в — это то самое отклоненное значение. После того, как мы поймали исключение, у нас есть несколько способов работы с ним:

  • Обработать исключение и вернуть нормальное значение. (Неиспользование выражения  в блоке  эквивалентно применениюкоторое также является нормальным значением.)
  • Выбросить ошибку, если хотите, чтобы функция вызова обработала её. Вы можете либо выбросить простой объект ошибки напрямую, с помощью , что позволит вам использовать функцию  в цепочке промисов (другими словами, вы все равно можете вызвать её следующим образом — ); другой вариант — можно обернуть ошибку с помощью объекта  например, , что позволит увидеть полную трассировку стека, когда эта ошибка будет отображаться в консоли.
  • Отклонить ошибку, например, . Это эквивалентно , поэтому не рекомендуется.

Преимущества использования :

  • Простой, традиционный способ. Если у вас есть опыт работы с другими языками, такими как Java или C ++, вам не составит труда понять данную концепцию.
  • Дает возможность поместить несколько вызовов в один блок  для обработки ошибок в одном месте, если обработка ошибок на каждом шаге не требуется.

В этом подходе есть и один недостаток. Так как  поймает любое исключение в блоке, то будут выброшены ошибки, которые в обычных случаях промисами не отлавливаются. Для того, чтобы понять эту идею, взгляните на пример:

class BookModel {
  fetchAll() {
    cb();    // note `cb` is undefined and will result an exception
    return fetch('/books');
  }
}
try {
  bookModel.fetchAll();
} catch(error) {
  console.log(error);  // This will print "cb is not defined"
}

Запустите этот код и вы получите ошибку в консоли, черного цвета. Ошибка выводилась с помощью но не самим JavaScript. Иногда это может быть фатальным: если заключен глубоко в ряд вызовов функций, и один из вызовов проглатывает ошибку, тогда будет очень сложно найти неопределенную ошибку, подобную этой.

Функция возвращает оба значения

Другой способ обработки ошибок практикуется в языке Go. Он позволяет async-функции возвращать как ошибку, так и результат. За более подробным описанием направляю вас в эту статью.

Перевод статьи Charlee Li: JavaScript async/await: The Good Part, Pitfalls and How to Use

Семейство subject-ов

Семейство subject-ов являются ярким примером горячих потоков. Эти классы являются неким гибридом, которые выступают одновременно в роли observable и observer. Так как subject является горячим потоком, то от него необходимо отписываться. Если говорить по основным методам, то это:

  • next – передача новых данных в поток
  • error – ошибка и завершение потока
  • complete – завершение потока
  • subscribe – подписаться на поток
  • unsubscribe – отписаться от потока
  • asObservable – трансформируем в наблюдателя
  • toPromise – трансформирует в промис

Выделяют 4 5 типов subject-ов.

Простой Subject – самый простой вид subject-ов. Создается без параметров. Передает значения пришедшие только после подписки.

BehaviorSubject – на мой взгляд самый распространённый вид subject-ов. На вход принимает значение по умолчанию. Всегда сохраняет данные последнего эмита, которые передает при подписке. Данный класс имеет так же полезный метод value, который возвращает текущее значение потока.

ReplaySubject — На вход опционально может принять первым аргументом размер буфера значений, которые он будет в себе хранить, а вторым время в течении которого нам нужны изменения.

AsyncSubject — при подписке ничего не происходит, и значение будет возвращено только при complete. Будет возвращено только последнее значение потока.

WebSocketSubject — О нем документация молчит и я сам его в первый раз вижу. Кто знает что он делает пишите, дополним.

Фуф. Ну вот мы и рассмотрели все, что я хотел сегодня рассказать. Надеюсь данная информация была полезной. Самостоятельно ознакомиться со списком литературы можно во вкладке полезная информация.

async

The attribute means that a script is completely independent:

  • The page doesn’t wait for async scripts, the contents are processed and displayed.
  • and async scripts don’t wait for each other:
    • may happen both before an async script (if an async script finishes loading after the page is complete)
    • …or after an async script (if an async script is short or was in HTTP-cache)
  • Other scripts don’t wait for scripts, and scripts don’t wait for them.

So, if we have several scripts, they may execute in any order. Whatever loads first – runs first:

  1. The page content shows up immediately: doesn’t block it.
  2. may happen both before and after , no guarantees here.
  3. Async scripts don’t wait for each other. A smaller script goes second, but probably loads before , so runs first. That’s called a “load-first” order.

Async scripts are great when we integrate an independent third-party script into the page: counters, ads and so on, as they don’t depend on our scripts, and our scripts shouldn’t wait for them:

New Abstract Base Classes

In order to allow better integration with existing frameworks (such as Tornado, see ) and compilers (such as Cython, see ), two new Abstract Base Classes (ABC) are added:

  • collections.abc.Awaitable ABC for Future-like classes, that implement __await__ method.

  • collections.abc.Coroutine ABC for coroutine objects, that implement send(value), throw(type, exc, tb), close() and __await__() methods.

    Note that generator-based coroutines with CO_ITERABLE_COROUTINE flag do not implement __await__ method, and therefore are not instances of collections.abc.Coroutine and collections.abc.Awaitable ABCs:

    @types.coroutine
    def gencoro():
        yield
    
    assert not isinstance(gencoro(), collections.abc.Coroutine)
    
    # however:
    assert inspect.isawaitable(gencoro())
    

To allow easy testing if objects support asynchronous iteration, two more ABCs are added:

Asynchronous Context Managers and «async with»

An asynchronous context manager is a context manager that is able to suspend execution in its enter and exit methods.

To make this possible, a new protocol for asynchronous context managers is proposed. Two new magic methods are added: __aenter__ and __aexit__. Both must return an awaitable.

An example of an asynchronous context manager:

class AsyncContextManager:
    async def __aenter__(self):
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        await log('exiting context')

A new statement for asynchronous context managers is proposed:

async with EXPR as VAR:
    BLOCK

which is semantically equivalent to:

mgr = (EXPR)
aexit = type(mgr).__aexit__
aenter = type(mgr).__aenter__

VAR = await aenter(mgr)
try:
    BLOCK
except:
    if not await aexit(mgr, *sys.exc_info()):
        raise
else:
    await aexit(mgr, None, None, None)

As with regular with statements, it is possible to specify multiple context managers in a single async with statement.

It is an error to pass a regular context manager without __aenter__ and __aexit__ methods to async with. It is a SyntaxError to use async with outside of an async def function.

Итого

Обычные итераторы и генераторы прекрасно работают с данными, которые не требуют времени для их создания или получения.

Когда мы ожидаем, что данные будут поступать асинхронно, с задержками, можно использовать их асинхронные аналоги и вместо.

Синтаксические различия между асинхронными и обычными итераторами:

Перебираемый объект Асинхронно перебираемый
Метод для получения итератора
возвращает промис, который завершается с

Синтаксические различия между асинхронными и обычными генераторами:

Генераторы Асинхронные генераторы
Объявление
возвращает промис, который завершается с

В веб-разработке мы часто встречаемся с потоками данных, когда они поступают по частям. Например, загрузка или выгрузка большого файла.

Мы можем использовать асинхронные генераторы для обработки таких данных. Также заметим, что в некоторых окружениях, например, браузерах, есть и другое API, называемое Streams (потоки), который предоставляет специальные интерфейсы для работы с такими потоками данных, их преобразования и передачи из одного потока в другой (например, загрузка из одного источника и сразу отправка в другое место).


С этим читают