| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 4dffd04 commit 877ab97
13 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,7 +8,8 @@ const common = require('../common.js'); | |||
| 8 | 8 | const { | |
| 9 | 9 | createHook, | |
| 10 | 10 | executionAsyncResource, | |
| 11 | - executionAsyncId | ||
| 11 | + executionAsyncId, | ||
| 12 | + AsyncLocalStorage | ||
| 12 | 13 | } = require('async_hooks'); | |
| 13 | 14 | const { createServer } = require('http'); | |
| 14 | 15 | ||
@@ -18,7 +19,7 @@ const connections = 500; | |||
| 18 | 19 | const path = '/'; | |
| 19 | 20 | ||
| 20 | 21 | const bench = common.createBenchmark(main, { | |
| 21 | - type: ['async-resource', 'destroy'], | ||
| 22 | + type: ['async-resource', 'destroy', 'async-local-storage'], | ||
| 22 | 23 | asyncMethod: ['callbacks', 'async'], | |
| 23 | 24 | n: [1e6] | |
| 24 | 25 | }); | |
@@ -102,6 +103,35 @@ function buildDestroy(getServe) { | |||
| 102 | 103 | } | |
| 103 | 104 | } | |
| 104 | 105 | ||
| 106 | + function buildAsyncLocalStorage(getServe) { | ||
| 107 | + const asyncLocalStorage = new AsyncLocalStorage(); | ||
| 108 | + const server = createServer((req, res) => { | ||
| 109 | + asyncLocalStorage.runSyncAndReturn(() => { | ||
| 110 | + getServe(getCLS, setCLS)(req, res); | ||
| 111 | + }); | ||
| 112 | + }); | ||
| 113 | + | ||
| 114 | + return { | ||
| 115 | + server, | ||
| 116 | + close | ||
| 117 | + }; | ||
| 118 | + | ||
| 119 | + function getCLS() { | ||
| 120 | + const store = asyncLocalStorage.getStore(); | ||
| 121 | + return store.get('store'); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + function setCLS(state) { | ||
| 125 | + const store = asyncLocalStorage.getStore(); | ||
| 126 | + store.set('store', state); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + function close() { | ||
| 130 | + asyncLocalStorage.disable(); | ||
| 131 | + server.close(); | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 105 | 135 | function getServeAwait(getCLS, setCLS) { | |
| 106 | 136 | return async function serve(req, res) { | |
| 107 | 137 | setCLS(Math.random()); | |
@@ -126,7 +156,8 @@ function getServeCallbacks(getCLS, setCLS) { | |||
| 126 | 156 | ||
| 127 | 157 | const types = { | |
| 128 | 158 | 'async-resource': buildCurrentResource, | |
| 129 | - 'destroy': buildDestroy | ||
| 159 | + 'destroy': buildDestroy, | ||
| 160 | + 'async-local-storage': buildAsyncLocalStorage | ||
| 130 | 161 | }; | |
| 131 | 162 | ||
| 132 | 163 | const asyncMethods = { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -859,6 +859,293 @@ for (let i = 0; i < 10; i++) { | |||
| 859 | 859 | } | |
| 860 | 860 | ``` | |
| 861 | 861 | ||
| 862 | + ## Class: `AsyncLocalStorage` | ||
| 863 | + <!-- YAML | ||
| 864 | + added: REPLACEME | ||
| 865 | + --> | ||
| 866 | + | ||
| 867 | + This class is used to create asynchronous state within callbacks and promise | ||
| 868 | + chains. It allows storing data throughout the lifetime of a web request | ||
| 869 | + or any other asynchronous duration. It is similar to thread-local storage | ||
| 870 | + in other languages. | ||
| 871 | + | ||
| 872 | + The following example builds a logger that will always know the current HTTP | ||
| 873 | + request and uses it to display enhanced logs without needing to explicitly | ||
| 874 | + provide the current HTTP request to it. | ||
| 875 | + | ||
| 876 | + ```js | ||
| 877 | + const { AsyncLocalStorage } = require('async_hooks'); | ||
| 878 | + const http = require('http'); | ||
| 879 | + | ||
| 880 | + const kReq = 'CURRENT_REQUEST'; | ||
| 881 | + const asyncLocalStorage = new AsyncLocalStorage(); | ||
| 882 | + | ||
| 883 | + function log(...args) { | ||
| 884 | + const store = asyncLocalStorage.getStore(); | ||
| 885 | + // Make sure the store exists and it contains a request. | ||
| 886 | + if (store && store.has(kReq)) { | ||
| 887 | + const req = store.get(kReq); | ||
| 888 | + // Prints `GET /items ERR could not do something | ||
| 889 | + console.log(req.method, req.url, ...args); | ||
| 890 | + } else { | ||
| 891 | + console.log(...args); | ||
| 892 | + } | ||
| 893 | + } | ||
| 894 | + | ||
| 895 | + http.createServer((request, response) => { | ||
| 896 | + asyncLocalStorage.run(() => { | ||
| 897 | + const store = asyncLocalStorage.getStore(); | ||
| 898 | + store.set(kReq, request); | ||
| 899 | + someAsyncOperation((err, result) => { | ||
| 900 | + if (err) { | ||
| 901 | + log('ERR', err.message); | ||
| 902 | + } | ||
| 903 | + }); | ||
| 904 | + }); | ||
| 905 | + }) | ||
| 906 | + .listen(8080); | ||
| 907 | + ``` | ||
| 908 | + | ||
| 909 | + When having multiple instances of `AsyncLocalStorage`, they are independent | ||
| 910 | + from each other. It is safe to instantiate this class multiple times. | ||
| 911 | + | ||
| 912 | + ### `new AsyncLocalStorage()` | ||
| 913 | + <!-- YAML | ||
| 914 | + added: REPLACEME | ||
| 915 | + --> | ||
| 916 | + | ||
| 917 | + Creates a new instance of `AsyncLocalStorage`. Store is only provided within a | ||
| 918 | + `run` or a `runSyncAndReturn` method call. | ||
| 919 | + | ||
| 920 | + ### `asyncLocalStorage.disable()` | ||
| 921 | + <!-- YAML | ||
| 922 | + added: REPLACEME | ||
| 923 | + --> | ||
| 924 | + | ||
| 925 | + This method disables the instance of `AsyncLocalStorage`. All subsequent calls | ||
| 926 | + to `asyncLocalStorage.getStore()` will return `undefined` until | ||
| 927 | + `asyncLocalStorage.run()` or `asyncLocalStorage.runSyncAndReturn()` | ||
| 928 | + is called again. | ||
| 929 | + | ||
| 930 | + When calling `asyncLocalStorage.disable()`, all current contexts linked to the | ||
| 931 | + instance will be exited. | ||
| 932 | + | ||
| 933 | + Calling `asyncLocalStorage.disable()` is required before the | ||
| 934 | + `asyncLocalStorage` can be garbage collected. This does not apply to stores | ||
| 935 | + provided by the `asyncLocalStorage`, as those objects are garbage collected | ||
| 936 | + along with the corresponding async resources. | ||
| 937 | + | ||
| 938 | + This method is to be used when the `asyncLocalStorage` is not in use anymore | ||
| 939 | + in the current process. | ||
| 940 | + | ||
| 941 | + ### `asyncLocalStorage.getStore()` | ||
| 942 | + <!-- YAML | ||
| 943 | + added: REPLACEME | ||
| 944 | + --> | ||
| 945 | + | ||
| 946 | + * Returns: {Map} | ||
| 947 | + | ||
| 948 | + This method returns the current store. | ||
| 949 | + If this method is called outside of an asynchronous context initialized by | ||
| 950 | + calling `asyncLocalStorage.run` or `asyncLocalStorage.runAndReturn`, it will | ||
| 951 | + return `undefined`. | ||
| 952 | + | ||
| 953 | + ### `asyncLocalStorage.run(callback[, ...args])` | ||
| 954 | + <!-- YAML | ||
| 955 | + added: REPLACEME | ||
| 956 | + --> | ||
| 957 | + | ||
| 958 | + * `callback` {Function} | ||
| 959 | + * `...args` {any} | ||
| 960 | + | ||
| 961 | + Calling `asyncLocalStorage.run(callback)` will create a new asynchronous | ||
| 962 | + context. | ||
| 963 | + Within the callback function and the asynchronous operations from the callback, | ||
| 964 | + `asyncLocalStorage.getStore()` will return an instance of `Map` known as | ||
| 965 | + "the store". This store will be persistent through the following | ||
| 966 | + asynchronous calls. | ||
| 967 | + | ||
| 968 | + The callback will be ran asynchronously. Optionally, arguments can be passed | ||
| 969 | + to the function. They will be passed to the callback function. | ||
| 970 | + | ||
| 971 | + If an error is thrown by the callback function, it will not be caught by | ||
| 972 | + a `try/catch` block as the callback is ran in a new asynchronous resource. | ||
| 973 | + Also, the stacktrace will be impacted by the asynchronous call. | ||
| 974 | + | ||
| 975 | + Example: | ||
| 976 | + | ||
| 977 | + ```js | ||
| 978 | + asyncLocalStorage.run(() => { | ||
| 979 | + asyncLocalStorage.getStore(); // Returns a Map | ||
| 980 | + someAsyncOperation(() => { | ||
| 981 | + asyncLocalStorage.getStore(); // Returns the same Map | ||
| 982 | + }); | ||
| 983 | + }); | ||
| 984 | + asyncLocalStorage.getStore(); // Returns undefined | ||
| 985 | + ``` | ||
| 986 | + | ||
| 987 | + ### `asyncLocalStorage.exit(callback[, ...args])` | ||
| 988 | + <!-- YAML | ||
| 989 | + added: REPLACEME | ||
| 990 | + --> | ||
| 991 | + | ||
| 992 | + * `callback` {Function} | ||
| 993 | + * `...args` {any} | ||
| 994 | + | ||
| 995 | + Calling `asyncLocalStorage.exit(callback)` will create a new asynchronous | ||
| 996 | + context. | ||
| 997 | + Within the callback function and the asynchronous operations from the callback, | ||
| 998 | + `asyncLocalStorage.getStore()` will return `undefined`. | ||
| 999 | + | ||
| 1000 | + The callback will be ran asynchronously. Optionally, arguments can be passed | ||
| 1001 | + to the function. They will be passed to the callback function. | ||
| 1002 | + | ||
| 1003 | + If an error is thrown by the callback function, it will not be caught by | ||
| 1004 | + a `try/catch` block as the callback is ran in a new asynchronous resource. | ||
| 1005 | + Also, the stacktrace will be impacted by the asynchronous call. | ||
| 1006 | + | ||
| 1007 | + Example: | ||
| 1008 | + | ||
| 1009 | + ```js | ||
| 1010 | + asyncLocalStorage.run(() => { | ||
| 1011 | + asyncLocalStorage.getStore(); // Returns a Map | ||
| 1012 | + asyncLocalStorage.exit(() => { | ||
| 1013 | + asyncLocalStorage.getStore(); // Returns undefined | ||
| 1014 | + }); | ||
| 1015 | + asyncLocalStorage.getStore(); // Returns the same Map | ||
| 1016 | + }); | ||
| 1017 | + ``` | ||
| 1018 | + | ||
| 1019 | + ### `asyncLocalStorage.runSyncAndReturn(callback[, ...args])` | ||
| 1020 | + <!-- YAML | ||
| 1021 | + added: REPLACEME | ||
| 1022 | + --> | ||
| 1023 | + | ||
| 1024 | + * `callback` {Function} | ||
| 1025 | + * `...args` {any} | ||
| 1026 | + | ||
| 1027 | + This methods runs a function synchronously within a context and return its | ||
| 1028 | + return value. The store is not accessible outside of the callback function or | ||
| 1029 | + the asynchronous operations created within the callback. | ||
| 1030 | + | ||
| 1031 | + Optionally, arguments can be passed to the function. They will be passed to | ||
| 1032 | + the callback function. | ||
| 1033 | + | ||
| 1034 | + If the callback function throws an error, it will be thrown by | ||
| 1035 | + `runSyncAndReturn` too. The stacktrace will not be impacted by this call and | ||
| 1036 | + the context will be exited. | ||
| 1037 | + | ||
| 1038 | + Example: | ||
| 1039 | + | ||
| 1040 | + ```js | ||
| 1041 | + try { | ||
| 1042 | + asyncLocalStorage.runSyncAndReturn(() => { | ||
| 1043 | + asyncLocalStorage.getStore(); // Returns a Map | ||
| 1044 | + throw new Error(); | ||
| 1045 | + }); | ||
| 1046 | + } catch (e) { | ||
| 1047 | + asyncLocalStorage.getStore(); // Returns undefined | ||
| 1048 | + // The error will be caught here | ||
| 1049 | + } | ||
| 1050 | + ``` | ||
| 1051 | + | ||
| 1052 | + ### `asyncLocalStorage.exitSyncAndReturn(callback[, ...args])` | ||
| 1053 | + <!-- YAML | ||
| 1054 | + added: REPLACEME | ||
| 1055 | + --> | ||
| 1056 | + | ||
| 1057 | + * `callback` {Function} | ||
| 1058 | + * `...args` {any} | ||
| 1059 | + | ||
| 1060 | + This methods runs a function synchronously outside of a context and return its | ||
| 1061 | + return value. The store is not accessible within the callback function or | ||
| 1062 | + the asynchronous operations created within the callback. | ||
| 1063 | + | ||
| 1064 | + Optionally, arguments can be passed to the function. They will be passed to | ||
| 1065 | + the callback function. | ||
| 1066 | + | ||
| 1067 | + If the callback function throws an error, it will be thrown by | ||
| 1068 | + `exitSyncAndReturn` too. The stacktrace will not be impacted by this call and | ||
| 1069 | + the context will be re-entered. | ||
| 1070 | + | ||
| 1071 | + Example: | ||
| 1072 | + | ||
| 1073 | + ```js | ||
| 1074 | + // Within a call to run or runSyncAndReturn | ||
| 1075 | + try { | ||
| 1076 | + asyncLocalStorage.getStore(); // Returns a Map | ||
| 1077 | + asyncLocalStorage.exitSyncAndReturn(() => { | ||
| 1078 | + asyncLocalStorage.getStore(); // Returns undefined | ||
| 1079 | + throw new Error(); | ||
| 1080 | + }); | ||
| 1081 | + } catch (e) { | ||
| 1082 | + asyncLocalStorage.getStore(); // Returns the same Map | ||
| 1083 | + // The error will be caught here | ||
| 1084 | + } | ||
| 1085 | + ``` | ||
| 1086 | + | ||
| 1087 | + ### Choosing between `run` and `runSyncAndReturn` | ||
| 1088 | + | ||
| 1089 | + #### When to choose `run` | ||
| 1090 | + | ||
| 1091 | + `run` is asynchronous. It is called with a callback function that | ||
| 1092 | + runs within a new asynchronous call. This is the most explicit behavior as | ||
| 1093 | + everything that is executed within the callback of `run` (including further | ||
| 1094 | + asynchronous operations) will have access to the store. | ||
| 1095 | + | ||
| 1096 | + If an instance of `AsyncLocalStorage` is used for error management (for | ||
| 1097 | + instance, with `process.setUncaughtExceptionCaptureCallback`), only | ||
| 1098 | + exceptions thrown in the scope of the callback function will be associated | ||
| 1099 | + with the context. | ||
| 1100 | + | ||
| 1101 | + This method is the safest as it provides strong scoping and consistent | ||
| 1102 | + behavior. | ||
| 1103 | + | ||
| 1104 | + It cannot be promisified using `util.promisify`. If needed, the `Promise` | ||
| 1105 | + constructor can be used: | ||
| 1106 | + | ||
| 1107 | + ```js | ||
| 1108 | + new Promise((resolve, reject) => { | ||
| 1109 | + asyncLocalStorage.run(() => { | ||
| 1110 | + someFunction((err, result) => { | ||
| 1111 | + if (err) { | ||
| 1112 | + return reject(err); | ||
| 1113 | + } | ||
| 1114 | + return resolve(result); | ||
| 1115 | + }); | ||
| 1116 | + }); | ||
| 1117 | + }); | ||
| 1118 | + ``` | ||
| 1119 | + | ||
| 1120 | + #### When to choose `runSyncAndReturn` | ||
| 1121 | + | ||
| 1122 | + `runSyncAndReturn` is synchronous. The callback function will be executed | ||
| 1123 | + synchronously and its return value will be returned by `runSyncAndReturn`. | ||
| 1124 | + The store will only be accessible from within the callback | ||
| 1125 | + function and the asynchronous operations created within this scope. | ||
| 1126 | + If the callback throws an error, `runSyncAndReturn` will throw it and it will | ||
| 1127 | + not be associated with the context. | ||
| 1128 | + | ||
| 1129 | + This method provides good scoping while being synchronous. | ||
| 1130 | + | ||
| 1131 | + #### Usage with `async/await` | ||
| 1132 | + | ||
| 1133 | + If, within an async function, only one `await` call is to run within a context, | ||
| 1134 | + the following pattern should be used: | ||
| 1135 | + | ||
| 1136 | + ```js | ||
| 1137 | + async function fn() { | ||
| 1138 | + await asyncLocalStorage.runSyncAndReturn(() => { | ||
| 1139 | + asyncLocalStorage.getStore().set('key', value); | ||
| 1140 | + return foo(); // The return value of foo will be awaited | ||
| 1141 | + }); | ||
| 1142 | + } | ||
| 1143 | + ``` | ||
| 1144 | + | ||
| 1145 | + In this example, the store is only available in the callback function and the | ||
| 1146 | + functions called by `foo`. Outside of `runSyncAndReturn`, calling `getStore` | ||
| 1147 | + will return `undefined`. | ||
| 1148 | + | ||
| 862 | 1149 | [`after` callback]: #async_hooks_after_asyncid | |
| 863 | 1150 | [`before` callback]: #async_hooks_before_asyncid | |
| 864 | 1151 | [`destroy` callback]: #async_hooks_destroy_asyncid | |
| Back | FazBrowse Home | New Git URL |
0 commit comments