| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5020f63 commit fbeb895
12 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -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 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,8 @@ const { | |||
| 8 | 8 | Symbol, | |
| 9 | 9 | } = primordials; | |
| 10 | 10 | ||
| 11 | + const promiseHooks = require('internal/promise_hooks'); | ||
| 12 | + | ||
| 11 | 13 | const async_wrap = internalBinding('async_wrap'); | |
| 12 | 14 | const { setCallbackTrampoline } = async_wrap; | |
| 13 | 15 | /* async_hook_fields is a Uint32Array wrapping the uint32_t array of | |
@@ -51,8 +53,6 @@ const { | |||
| 51 | 53 | executionAsyncResource: executionAsyncResource_, | |
| 52 | 54 | clearAsyncIdStack, | |
| 53 | 55 | } = async_wrap; | |
| 54 | - // For performance reasons, only track Promises when a hook is enabled. | ||
| 55 | - const { setPromiseHooks } = async_wrap; | ||
| 56 | 56 | // Properties in active_hooks are used to keep track of the set of hooks being | |
| 57 | 57 | // executed in case another hook is enabled/disabled. The new set of hooks is | |
| 58 | 58 | // then restored once the active set of hooks is finished executing. | |
@@ -374,6 +374,7 @@ function enableHooks() { | |||
| 374 | 374 | async_hook_fields[kCheck] += 1; | |
| 375 | 375 | } | |
| 376 | 376 | ||
| 377 | + let stopPromiseHook; | ||
| 377 | 378 | function updatePromiseHookMode() { | |
| 378 | 379 | wantPromiseHook = true; | |
| 379 | 380 | let initHook; | |
@@ -383,12 +384,13 @@ function updatePromiseHookMode() { | |||
| 383 | 384 | } else if (destroyHooksExist()) { | |
| 384 | 385 | initHook = destroyTracking; | |
| 385 | 386 | } | |
| 386 | - setPromiseHooks( | ||
| 387 | - initHook, | ||
| 388 | - promiseBeforeHook, | ||
| 389 | - promiseAfterHook, | ||
| 390 | - promiseResolveHooksExist() ? promiseResolveHook : undefined, | ||
| 391 | - ); | ||
| 387 | + if (stopPromiseHook) stopPromiseHook(); | ||
| 388 | + stopPromiseHook = promiseHooks.createHook({ | ||
| 389 | + init: initHook, | ||
| 390 | + before: promiseBeforeHook, | ||
| 391 | + after: promiseAfterHook, | ||
| 392 | + settled: promiseResolveHooksExist() ? promiseResolveHook : undefined | ||
| 393 | + }); | ||
| 392 | 394 | } | |
| 393 | 395 | ||
| 394 | 396 | function disableHooks() { | |
@@ -402,8 +404,8 @@ function disableHooks() { | |||
| 402 | 404 | } | |
| 403 | 405 | ||
| 404 | 406 | function disablePromiseHookIfNecessary() { | |
| 405 | - if (!wantPromiseHook) { | ||
| 406 | - setPromiseHooks(undefined, undefined, undefined, undefined); | ||
| 407 | + if (!wantPromiseHook && stopPromiseHook) { | ||
| 408 | + stopPromiseHook(); | ||
| 407 | 409 | } | |
| 408 | 410 | } | |
| 409 | 411 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments