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