Асинхронное отслеживание контекста¶
Стабильность: 2 – Стабильная
АПИ является удовлетворительным. Совместимость с NPM имеет высший приоритет и не будет нарушена кроме случаев явной необходимости.
Введение¶
Эти классы используются для связывания состояния и его распространения через обратные вызовы и цепочки промисов. Они позволяют хранить данные на протяжении всего времени жизни веб-запроса или любого другого асинхронного процесса. Это похоже на локальное хранилище потока в других языках.
Классы AsyncLocalStorage и AsyncResource являются частью модуля node:async_hooks:
1 2 3 4 | |
1 2 3 4 | |
Класс: AsyncLocalStorage¶
Этот класс создаёт хранилища, которые сохраняют целостность при асинхронных операциях.
Хотя можно создать собственную реализацию поверх модуля node:async_hooks, предпочтительнее использовать AsyncLocalStorage, поскольку это производительная и безопасная с точки зрения памяти реализация со значительными оптимизациями, которые неочевидно реализовать самостоятельно.
Следующий пример использует AsyncLocalStorage для создания простого логгера, который присваивает идентификаторы входящим HTTP-запросам и включает их в сообщения, записываемые в журнал в рамках каждого запроса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
Каждый экземпляр AsyncLocalStorage поддерживает независимый контекст хранения. Несколько экземпляров могут безопасно существовать одновременно без риска вмешательства в данные друг друга.
new AsyncLocalStorage([options])¶
options<Object>
Создаёт новый экземпляр AsyncLocalStorage. Хранилище доступно только внутри вызова run() или после вызова enterWith().
Статический метод: AsyncLocalStorage.bind(fn)¶
fn<Function>Функция, которую нужно привязать к текущему контексту выполнения.- Возвращает:
<Function>Новую функцию, которая вызываетfnв захваченном контексте выполнения.
Привязывает переданную функцию к текущему контексту выполнения.
Статический метод: AsyncLocalStorage.snapshot()¶
- Возвращает:
<Function>Новую функцию с сигнатурой(fn: (...args) : R, ...args) : R.
Захватывает текущий контекст выполнения и возвращает функцию, принимающую другую функцию в качестве аргумента. Каждый раз, когда вызывается возвращённая функция, она вызывает переданную ей функцию в захваченном контексте.
1 2 3 4 5 6 7 8 | |
AsyncLocalStorage.snapshot() может заменить использование AsyncResource для простых задач отслеживания асинхронного контекста, например:
1 2 3 4 5 6 7 8 9 10 11 12 | |
asyncLocalStorage.disable()¶
Стабильность: 1 – Экспериментальная
Экспериментальный
Отключает экземпляр AsyncLocalStorage. Все последующие вызовы asyncLocalStorage.getStore() будут возвращать undefined, пока снова не будет вызван asyncLocalStorage.run() или asyncLocalStorage.enterWith().
При вызове asyncLocalStorage.disable() будут завершены все текущие контексты, связанные с этим экземпляром.
Вызов asyncLocalStorage.disable() обязателен перед тем, как asyncLocalStorage сможет быть освобождён сборщиком мусора. Это не относится к хранилищам, предоставляемым asyncLocalStorage, поскольку эти объекты собираются вместе с соответствующими асинхронными ресурсами.
Используйте этот метод, когда asyncLocalStorage больше не используется в текущем процессе.
asyncLocalStorage.getStore()¶
- Возвращает:
<any>
Возвращает текущее хранилище. Если метод вызывается вне асинхронного контекста, инициализированного вызовом asyncLocalStorage.run() или asyncLocalStorage.enterWith(), он возвращает undefined.
asyncLocalStorage.enterWith(store)¶
Стабильность: 1 – Экспериментальная
Экспериментальный
store<any>
Переходит в контекст на оставшуюся часть текущего синхронного выполнения, а затем сохраняет хранилище во всех последующих асинхронных вызовах.
Пример:
1 2 3 4 5 6 7 | |
Этот переход продолжается в течение всего синхронного выполнения. Это означает, что, например, если войти в контекст внутри обработчика события, то последующие обработчики этого события также будут выполняться в данном контексте, если только они явно не привязаны к другому контексту через AsyncResource. Поэтому run() следует предпочитать enterWith(), если только нет серьёзных причин использовать именно этот метод.
1 2 3 4 5 6 7 8 9 10 11 12 | |
asyncLocalStorage.name¶
- Тип:
<string>
Имя экземпляра AsyncLocalStorage, если оно задано.
asyncLocalStorage.run(store, callback[, ...args])¶
store<any>callback<Function>...args<any>
Синхронно выполняет функцию внутри контекста и возвращает её возвращаемое значение. Хранилище недоступно вне функции обратного вызова. Хранилище доступно для любых асинхронных операций, созданных внутри этой функции.
Необязательные args передаются в функцию обратного вызова.
Если функция обратного вызова выбрасывает ошибку, run() тоже выбрасывает её. Этот вызов не влияет на трассировку стека, и контекст завершается.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
asyncLocalStorage.exit(callback[, ...args])¶
Стабильность: 1 – Экспериментальная
Экспериментальный
callback<Function>...args<any>
Синхронно выполняет функцию вне контекста и возвращает её возвращаемое значение. Хранилище недоступно внутри функции обратного вызова или асинхронных операций, созданных в её рамках. Любой вызов getStore(), выполненный внутри функции обратного вызова, всегда будет возвращать undefined.
Необязательные args передаются в функцию обратного вызова.
Если функция обратного вызова выбрасывает ошибку, exit() тоже выбрасывает её. Этот вызов не влияет на трассировку стека, а контекст затем восстанавливается.
Пример:
1 2 3 4 5 6 7 8 9 10 11 | |
asyncLocalStorage.withScope(store)¶
Стабильность: 1 – Экспериментальная
Экспериментальный
store<any>- Возвращает:
<RunScope>
Создаёт освобождаемую область видимости, которая входит в переданное хранилище и автоматически восстанавливает предыдущее значение хранилища при освобождении. Этот метод предназначен для работы с механизмом явного управления ресурсами в JavaScript (синтаксис using).
Пример:
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 6 7 8 9 10 | |
Метод withScope() особенно полезен для управления контекстом в синхронном коде, когда нужно гарантировать восстановление предыдущего значения хранилища при выходе из блока, даже если была выброшена ошибка.
1 2 3 4 5 6 7 8 9 10 11 12 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
Важно: при использовании withScope() в async-функциях до первого await учитывайте, что изменение области видимости повлияет на контекст вызывающего кода. Синхронная часть async-функции (до первого await) выполняется немедленно при вызове, а при достижении первого await она возвращает вызывающему промис. В этот момент изменение области видимости становится видимым в контексте вызывающего кода и будет сохраняться в последующем синхронном коде, пока что-то ещё не изменит её значение. Для асинхронных операций предпочтительнее использовать run(), поскольку он корректно изолирует контекст на границах асинхронности.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Использование с async/await¶
Если внутри async-функции только один вызов await должен выполняться в контексте, следует использовать следующий шаблон:
1 2 3 4 5 6 | |
В этом примере хранилище доступно только в функции обратного вызова и в функциях, вызываемых foo. Вне run вызов getStore вернёт undefined.
Устранение неполадок: потеря контекста¶
В большинстве случаев AsyncLocalStorage работает без проблем. В редких ситуациях текущее хранилище теряется в одной из асинхронных операций.
Если ваш код основан на функциях обратного вызова, достаточно промисифицировать его с помощью util.promisify(), чтобы он начал работать с нативными промисами.
Если вам нужно использовать API на основе функций обратного вызова или ваш код предполагает пользовательскую реализацию thenable, используйте класс AsyncResource для связывания асинхронной операции с правильным контекстом выполнения. Найдите вызов функции, ответственный за потерю контекста, логируя содержимое asyncLocalStorage.getStore() после вызовов, которые вы подозреваете в этой потере. Когда в логе появляется undefined, вероятнее всего, за потерю контекста отвечает последняя вызванная функция обратного вызова.
Класс: RunScope¶
Стабильность: 1 – Экспериментальная
Экспериментальный
Освобождаемая область видимости, возвращаемая asyncLocalStorage.withScope(), которая автоматически восстанавливает предыдущее значение хранилища при освобождении. Этот класс реализует протокол явного управления ресурсами и предназначен для работы с синтаксисом using в JavaScript.
Область видимости автоматически восстанавливает предыдущее значение хранилища при выходе из блока using как при обычном завершении, так и при выбрасывании ошибки.
scope.dispose()¶
Явно завершает область видимости и восстанавливает предыдущее значение хранилища. Этот метод идемпотентен: многократный вызов имеет тот же эффект, что и однократный.
Метод [Symbol.dispose]() делегирует dispose().
Если withScope() вызывается без ключевого слова using, для восстановления предыдущего значения хранилища необходимо вручную вызвать dispose(). Если забыть вызвать dispose(), значение хранилища сохранится на оставшуюся часть текущего контекста выполнения:
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 6 7 8 9 10 | |
Класс: AsyncResource¶
Класс AsyncResource предназначен для расширения асинхронными ресурсами, создаваемыми embedder-ом. С его помощью пользователи могут легко инициировать события жизненного цикла собственных ресурсов.
Хук init срабатывает при создании экземпляра AsyncResource.
Ниже приведён обзор API AsyncResource.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
new AsyncResource(type[, options])¶
type<string>Тип асинхронного события.options<Object>triggerAsyncId<number>ID контекста выполнения, создавшего это асинхронное событие. По умолчанию:executionAsyncId().requireManualDestroy<boolean>Если установлено вtrue, отключаетemitDestroyпри сборке объекта мусорщиком. Обычно это значение задавать не требуется (даже еслиemitDestroyвызывается вручную), кроме случая, когда был полученasyncIdресурса и с ним вызываетсяemitDestroyчувствительного API. Если установлено вfalse, вызовemitDestroyпри сборке мусора произойдёт только при наличии хотя бы одного активного хукаdestroy. По умолчанию:false.
Пример использования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Статический метод: AsyncResource.bind(fn[, type[, thisArg]])¶
fn<Function>Функция, которую нужно привязать к текущему контексту выполнения.type<string>Необязательное имя, связанное с базовымAsyncResource.thisArg<any>
Привязывает переданную функцию к текущему контексту выполнения.
asyncResource.bind(fn[, thisArg])¶
fn<Function>Функция, которую нужно привязать к текущемуAsyncResource.thisArg<any>
Привязывает переданную функцию к выполнению в области видимости этого AsyncResource.
asyncResource.runInAsyncScope(fn[, thisArg, ...args])¶
fn<Function>Функция, которую нужно вызвать в контексте выполнения этого асинхронного ресурса.thisArg<any>Получатель, который будет использован при вызове функции....args<any>Необязательные аргументы, передаваемые в функцию.
Вызывает переданную функцию с указанными аргументами в контексте выполнения асинхронного ресурса. Это установит контекст, вызовет AsyncHooks before для callback-функций, вызовет функцию, затем AsyncHooks after и после этого восстановит исходный контекст выполнения.
asyncResource.emitDestroy()¶
- Возвращает:
<AsyncResource>Ссылку наasyncResource.
Вызывает все хуки destroy. Этот метод должен вызываться только один раз. Если вызвать его более одного раза, будет выброшена ошибка. Его необходимо вызывать вручную. Если ресурс будет просто собран GC, хуки destroy никогда не будут вызваны.
asyncResource.asyncId()¶
- Возвращает:
<number>УникальныйasyncId, присвоенный ресурсу.
asyncResource.triggerAsyncId()¶
- Возвращает:
<number>Тот жеtriggerAsyncId, который передаётся в конструкторAsyncResource.
Использование AsyncResource для пула потоков Worker¶
Следующий пример показывает, как использовать класс AsyncResource для корректного асинхронного отслеживания в пуле Worker. Другие пулы ресурсов, например пулы подключений к базам данных, могут использовать аналогичную модель.
Предположим, задача состоит в сложении двух чисел, а файл task_processor.js содержит следующий код:
1 2 3 4 | |
1 2 3 4 | |
Пул Worker вокруг него мог бы использовать следующую структуру:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
Без явного отслеживания, которое добавляют объекты WorkerPoolTaskInfo, могло бы показаться, что callback-функции связаны с отдельными объектами Worker. Однако создание Worker не связано с созданием задач и не даёт информации о том, когда эти задачи были запланированы.
Этот пул можно использовать следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
Интеграция AsyncResource с EventEmitter¶
Слушатели событий, запускаемые EventEmitter, могут выполняться в другом контексте выполнения, чем тот, который был активен во время вызова eventEmitter.on().
Следующий пример показывает, как использовать класс AsyncResource для правильного связывания слушателя события с корректным контекстом выполнения. Тот же подход можно применить к Stream или аналогичному классу, управляемому событиями.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |