@@ -562,8 +562,267 @@ added: v8.0.0
562
562
A subclass of [ ` Deserializer ` ] [ ] corresponding to the format written by
563
563
[ ` DefaultSerializer ` ] [ ] .
564
564
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
+
565
822
[ HTML structured clone algorithm ] : https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
823
+ [ Hook Callbacks ] : #hook_callbacks
566
824
[ V8 ] : https://developers.google.com/v8/
825
+ [ `AsyncLocalStorage` ] : async_context.md#class_asynclocalstorage
567
826
[ `Buffer` ] : buffer.md
568
827
[ `DefaultDeserializer` ] : #class-v8defaultdeserializer
569
828
[ `DefaultSerializer` ] : #class-v8defaultserializer
@@ -573,15 +832,20 @@ A subclass of [`Deserializer`][] corresponding to the format written by
573
832
[ `GetHeapSpaceStatistics` ] : https://v8docs.nodesource.com/node-13.2/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4
574
833
[ `NODE_V8_COVERAGE` ] : cli.md#node_v8_coveragedir
575
834
[ `Serializer` ] : #class-v8serializer
835
+ [ `after` callback ] : #after_promise
836
+ [ `async_hooks` ] : async_hooks.md
837
+ [ `before` callback ] : #before_promise
576
838
[ `buffer.constants.MAX_LENGTH` ] : buffer.md#bufferconstantsmax_length
577
839
[ `deserializer._readHostObject()` ] : #deserializer_readhostobject
578
840
[ `deserializer.transferArrayBuffer()` ] : #deserializertransferarraybufferid-arraybuffer
841
+ [ `init` callback ] : #init_promise_parent
579
842
[ `serialize()` ] : #v8serializevalue
580
843
[ `serializer._getSharedArrayBufferId()` ] : #serializer_getsharedarraybufferidsharedarraybuffer
581
844
[ `serializer._writeHostObject()` ] : #serializer_writehostobjectobject
582
845
[ `serializer.releaseBuffer()` ] : #serializerreleasebuffer
583
846
[ `serializer.transferArrayBuffer()` ] : #serializertransferarraybufferid-arraybuffer
584
847
[ `serializer.writeRawBytes()` ] : #serializerwriterawbytesbuffer
848
+ [ `settled` callback ] : #settled_promise
585
849
[ `v8.stopCoverage()` ] : #v8stopcoverage
586
850
[ `v8.takeCoverage()` ] : #v8takecoverage
587
851
[ `vm.Script` ] : vm.md#new-vmscriptcode-options
0 commit comments