@@ -564,8 +564,267 @@ added: v8.0.0
564564A subclass of [ ` Deserializer ` ] [ ] corresponding to the format written by
565565[ ` DefaultSerializer ` ] [ ] .
566566
567+ ## Promise hooks
568+
569+ The ` promiseHooks ` interface can be used to track promise lifecycle events.
570+ To track _ all_ async activity, see [ ` async_hooks ` ] [ ] which internally uses this
571+ module to produce promise lifecycle events in addition to events for other
572+ async resources. For request context management, see [ ` AsyncLocalStorage ` ] [ ] .
573+
574+ ``` mjs
575+ import { promiseHooks } from ' v8' ;
576+
577+ // There are four lifecycle events produced by promises:
578+
579+ // The `init` event represents the creation of a promise. This could be a
580+ // direct creation such as with `new Promise(...)` or a continuation such
581+ // as `then()` or `catch()`. It also happens whenever an async function is
582+ // called or does an `await`. If a continuation promise is created, the
583+ // `parent` will be the promise it is a continuation from.
584+ function init (promise , parent ) {
585+ console .log (' a promise was created' , { promise, parent });
586+ }
587+
588+ // The `settled` event happens when a promise receives a resolution or
589+ // rejection value. This may happen synchronously such as when using
590+ // `Promise.resolve()` on non-promise input.
591+ function settled (promise ) {
592+ console .log (' a promise resolved or rejected' , { promise });
593+ }
594+
595+ // The `before` event runs immediately before a `then()` or `catch()` handler
596+ // runs or an `await` resumes execution.
597+ function before (promise ) {
598+ console .log (' a promise is about to call a then handler' , { promise });
599+ }
600+
601+ // The `after` event runs immediately after a `then()` handler runs or when
602+ // an `await` begins after resuming from another.
603+ function after (promise ) {
604+ console .log (' a promise is done calling a then handler' , { promise });
605+ }
606+
607+ // Lifecycle hooks may be started and stopped individually
608+ const stopWatchingInits = promiseHooks .onInit (init);
609+ const stopWatchingSettleds = promiseHooks .onSettled (settled);
610+ const stopWatchingBefores = promiseHooks .onBefore (before);
611+ const stopWatchingAfters = promiseHooks .onAfter (after);
612+
613+ // Or they may be started and stopped in groups
614+ const stopHookSet = promiseHooks .createHook ({
615+ init,
616+ settled,
617+ before,
618+ after
619+ });
620+
621+ // To stop a hook, call the function returned at its creation.
622+ stopWatchingInits ();
623+ stopWatchingSettleds ();
624+ stopWatchingBefores ();
625+ stopWatchingAfters ();
626+ stopHookSet ();
627+ ```
628+
629+ ### ` promiseHooks.onInit(init) `
630+ <!-- YAML
631+ added: REPLACEME
632+ -->
633+
634+ * ` init ` {Function} The [ ` init ` callback] [ ] to call when a promise is created.
635+ * Returns: {Function} Call to stop the hook.
636+
637+ ** The ` init ` hook must be a plain function. Providing an async function will
638+ throw as it would produce an infinite microtask loop.**
639+
640+ ``` mjs
641+ import { promiseHooks } from ' v8' ;
642+
643+ const stop = promiseHooks .onInit ((promise , parent ) => {});
644+ ```
645+
646+ ``` cjs
647+ const { promiseHooks } = require (' v8' );
648+
649+ const stop = promiseHooks .onInit ((promise , parent ) => {});
650+ ```
651+
652+ ### ` promiseHooks.onSettled(settled) `
653+ <!-- YAML
654+ added: REPLACEME
655+ -->
656+
657+ * ` settled ` {Function} The [ ` settled ` callback] [ ] to call when a promise
658+ is resolved or rejected.
659+ * Returns: {Function} Call to stop the hook.
660+
661+ ** The ` settled ` hook must be a plain function. Providing an async function will
662+ throw as it would produce an infinite microtask loop.**
663+
664+ ``` mjs
665+ import { promiseHooks } from ' v8' ;
666+
667+ const stop = promiseHooks .onSettled ((promise ) => {});
668+ ```
669+
670+ ``` cjs
671+ const { promiseHooks } = require (' v8' );
672+
673+ const stop = promiseHooks .onSettled ((promise ) => {});
674+ ```
675+
676+ ### ` promiseHooks.onBefore(before) `
677+ <!-- YAML
678+ added: REPLACEME
679+ -->
680+
681+ * ` before ` {Function} The [ ` before ` callback] [ ] to call before a promise
682+ continuation executes.
683+ * Returns: {Function} Call to stop the hook.
684+
685+ ** The ` before ` hook must be a plain function. Providing an async function will
686+ throw as it would produce an infinite microtask loop.**
687+
688+ ``` mjs
689+ import { promiseHooks } from ' v8' ;
690+
691+ const stop = promiseHooks .onBefore ((promise ) => {});
692+ ```
693+
694+ ``` cjs
695+ const { promiseHooks } = require (' v8' );
696+
697+ const stop = promiseHooks .onBefore ((promise ) => {});
698+ ```
699+
700+ ### ` promiseHooks.onAfter(after) `
701+ <!-- YAML
702+ added: REPLACEME
703+ -->
704+
705+ * ` after ` {Function} The [ ` after ` callback] [ ] to call after a promise
706+ continuation executes.
707+ * Returns: {Function} Call to stop the hook.
708+
709+ ** The ` after ` hook must be a plain function. Providing an async function will
710+ throw as it would produce an infinite microtask loop.**
711+
712+ ``` mjs
713+ import { promiseHooks } from ' v8' ;
714+
715+ const stop = promiseHooks .onAfter ((promise ) => {});
716+ ```
717+
718+ ``` cjs
719+ const { promiseHooks } = require (' v8' );
720+
721+ const stop = promiseHooks .onAfter ((promise ) => {});
722+ ```
723+
724+ ### ` promiseHooks.createHook(callbacks) `
725+ <!-- YAML
726+ added: REPLACEME
727+ -->
728+
729+ * ` callbacks ` {Object} The [ Hook Callbacks] [ ] to register
730+ * ` init ` {Function} The [ ` init ` callback] [ ] .
731+ * ` before ` {Function} The [ ` before ` callback] [ ] .
732+ * ` after ` {Function} The [ ` after ` callback] [ ] .
733+ * ` settled ` {Function} The [ ` settled ` callback] [ ] .
734+ * Returns: {Function} Used for disabling hooks
735+
736+ ** The hook callbacks must be plain functions. Providing async functions will
737+ throw as it would produce an infinite microtask loop.**
738+
739+ Registers functions to be called for different lifetime events of each promise.
740+
741+ The callbacks ` init() ` /` before() ` /` after() ` /` settled() ` are called for the
742+ respective events during a promise's lifetime.
743+
744+ All callbacks are optional. For example, if only promise creation needs to
745+ be tracked, then only the ` init ` callback needs to be passed. The
746+ specifics of all functions that can be passed to ` callbacks ` is in the
747+ [ Hook Callbacks] [ ] section.
748+
749+ ``` mjs
750+ import { promiseHooks } from ' v8' ;
751+
752+ const stopAll = promiseHooks .createHook ({
753+ init (promise , parent ) {}
754+ });
755+ ```
756+
757+ ``` cjs
758+ const { promiseHooks } = require (' v8' );
759+
760+ const stopAll = promiseHooks .createHook ({
761+ init (promise , parent ) {}
762+ });
763+ ```
764+
765+ ### Hook callbacks
766+
767+ Key events in the lifetime of a promise have been categorized into four areas:
768+ creation of a promise, before/after a continuation handler is called or around
769+ an await, and when the promise resolves or rejects.
770+
771+ While these hooks are similar to those of [ ` async_hooks ` ] [ ] they lack a
772+ ` destroy ` hook. Other types of async resources typically represent sockets or
773+ file descriptors which have a distinct "closed" state to express the ` destroy `
774+ lifecycle event while promises remain usable for as long as code can still
775+ reach them. Garbage collection tracking is used to make promises fit into the
776+ ` async_hooks ` event model, however this tracking is very expensive and they may
777+ not necessarily ever even be garbage collected.
778+
779+ Because promises are asynchronous resources whose lifecycle is tracked
780+ via the promise hooks mechanism, the ` init() ` , ` before() ` , ` after() ` , and
781+ ` settled() ` callbacks * must not* be async functions as they create more
782+ promises which would produce an infinite loop.
783+
784+ While this API is used to feed promise events into [ ` async_hooks ` ] [ ] , the
785+ ordering between the two is considered undefined. Both APIs are multi-tenant
786+ and therefore could produce events in any order relative to each other.
787+
788+ #### ` init(promise, parent) `
789+
790+ * ` promise ` {Promise} The promise being created.
791+ * ` parent ` {Promise} The promise continued from, if applicable.
792+
793+ Called when a promise is constructed. This _ does not_ mean that corresponding
794+ ` before ` /` after ` events will occur, only that the possibility exists. This will
795+ happen if a promise is created without ever getting a continuation.
796+
797+ #### ` before(promise) `
798+
799+ * ` promise ` {Promise}
800+
801+ Called before a promise continuation executes. This can be in the form of
802+ ` then() ` , ` catch() ` , or ` finally() ` handlers or an ` await ` resuming.
803+
804+ The ` before ` callback will be called 0 to N times. The ` before ` callback
805+ will typically be called 0 times if no continuation was ever made for the
806+ promise. The ` before ` callback may be called many times in the case where
807+ many continuations have been made from the same promise.
808+
809+ #### ` after(promise) `
810+
811+ * ` promise ` {Promise}
812+
813+ Called immediately after a promise continuation executes. This may be after a
814+ ` then() ` , ` catch() ` , or ` finally() ` handler or before an ` await ` after another
815+ ` await ` .
816+
817+ #### ` settled(promise) `
818+
819+ * ` promise ` {Promise}
820+
821+ Called when the promise receives a resolution or rejection value. This may
822+ occur synchronously in the case of ` Promise.resolve() ` or ` Promise.reject() ` .
823+
567824[ HTML structured clone algorithm ] : https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
825+ [ Hook Callbacks ] : #hook_callbacks
568826[ V8 ] : https://developers.google.com/v8/
827+ [ `AsyncLocalStorage` ] : async_context.md#class_asynclocalstorage
569828[ `Buffer` ] : buffer.md
570829[ `DefaultDeserializer` ] : #class-v8defaultdeserializer
571830[ `DefaultSerializer` ] : #class-v8defaultserializer
@@ -575,15 +834,20 @@ A subclass of [`Deserializer`][] corresponding to the format written by
575834[ `GetHeapSpaceStatistics` ] : https://v8docs.nodesource.com/node-13.2/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4
576835[ `NODE_V8_COVERAGE` ] : cli.md#node_v8_coveragedir
577836[ `Serializer` ] : #class-v8serializer
837+ [ `after` callback ] : #after_promise
838+ [ `async_hooks` ] : async_hooks.md
839+ [ `before` callback ] : #before_promise
578840[ `buffer.constants.MAX_LENGTH` ] : buffer.md#bufferconstantsmax_length
579841[ `deserializer._readHostObject()` ] : #deserializer_readhostobject
580842[ `deserializer.transferArrayBuffer()` ] : #deserializertransferarraybufferid-arraybuffer
843+ [ `init` callback ] : #init_promise_parent
581844[ `serialize()` ] : #v8serializevalue
582845[ `serializer._getSharedArrayBufferId()` ] : #serializer_getsharedarraybufferidsharedarraybuffer
583846[ `serializer._writeHostObject()` ] : #serializer_writehostobjectobject
584847[ `serializer.releaseBuffer()` ] : #serializerreleasebuffer
585848[ `serializer.transferArrayBuffer()` ] : #serializertransferarraybufferid-arraybuffer
586849[ `serializer.writeRawBytes()` ] : #serializerwriterawbytesbuffer
850+ [ `settled` callback ] : #settled_promise
587851[ `v8.stopCoverage()` ] : #v8stopcoverage
588852[ `v8.takeCoverage()` ] : #v8takecoverage
589853[ `vm.Script` ] : vm.md#new-vmscriptcode-options
0 commit comments