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