From 4f650bd5f18bc4f01ecc933e0c2ff3a7304859d1 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Wed, 11 Sep 2024 10:43:34 +0200 Subject: [PATCH 1/9] feat(progress-bar): setup basic --- package-lock.json | 42 +++++++++++++++++++++++++++ package.json | 1 + src/generators/legacy-html/index.mjs | 13 +++++++++ src/loader.mjs | 14 +++++++++ src/utils/progressBar.mjs | 43 ++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 src/utils/progressBar.mjs diff --git a/package-lock.json b/package-lock.json index d3e7ea5..13f32e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "@node-core/api-docs-tooling", "dependencies": { + "cli-progress": "^3.12.0", "commander": "^12.1.0", "github-slugger": "^2.0.0", "glob": "^11.0.0", @@ -653,6 +654,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-progress/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cli-progress/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", diff --git a/package.json b/package.json index c9330b1..3c9026d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "prettier": "3.3.3" }, "dependencies": { + "cli-progress": "^3.12.0", "commander": "^12.1.0", "github-slugger": "^2.0.0", "glob": "^11.0.0", diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index d36e990..07f7889 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -11,6 +11,12 @@ import tableOfContents from './utils/tableOfContents.mjs'; import { groupNodesByModule } from '../../utils/generators.mjs'; import { getRemarkRehype } from '../../utils/remark.mjs'; +import { + createProgressBar, + startProgressBar, + updateProgressBar, + stopProgressBar, +} from '../../utils/progressBar.mjs'; /** * @typedef {{ @@ -147,6 +153,10 @@ export default { return replaceTemplateValues(generatedTemplate); }; + // Creates a progress bar to show the progress of the generation process + const progressBar = createProgressBar('Generating HTML files'); + startProgressBar(progressBar, headNodes.length); + for (const node of headNodes) { const result = processModuleNodes(node); @@ -157,7 +167,10 @@ export default { }); await writeFile(join(output, `${node.api}.html`), minified); + updateProgressBar(progressBar); } + // Stops the progress bar and clears the line + stopProgressBar(progressBar); // Define the output folder for API docs assets const assetsFolder = join(output, 'assets'); diff --git a/src/loader.mjs b/src/loader.mjs index b3ae300..bb0062a 100644 --- a/src/loader.mjs +++ b/src/loader.mjs @@ -6,6 +6,13 @@ import { extname } from 'node:path'; import { globSync } from 'glob'; import { VFile } from 'vfile'; +import { + createProgressBar, + startProgressBar, + updateProgressBar, + stopProgressBar, +} from './utils/progressBar.mjs'; + /** * This method creates a simple abstract "Loader", which technically * could be used for different things, but here we want to use it to load @@ -26,8 +33,15 @@ const createLoader = () => { filePath => extname(filePath) === '.md' ); + const progressBar = createProgressBar('Loading files'); + startProgressBar(progressBar, resolvedFiles.length); + return resolvedFiles.map(async filePath => { const fileContents = await readFile(filePath, 'utf-8'); + updateProgressBar(progressBar); + // normally we stop the progress bar when the loop is done + // but here we return the loop so we need to stop it when the last file is loaded + if (progressBar.value === progressBar.total) stopProgressBar(progressBar); return new VFile({ path: filePath, value: fileContents }); }); diff --git a/src/utils/progressBar.mjs b/src/utils/progressBar.mjs new file mode 100644 index 0000000..8b1637a --- /dev/null +++ b/src/utils/progressBar.mjs @@ -0,0 +1,43 @@ +import { styleText } from 'node:util'; + +import cliProgress from 'cli-progress'; + +/** + * + * @param {string} label + * @returns {import('cli-progress').SingleBar} + */ +export function createProgressBar(label = '') { + const format = ` ${styleText(['bold', 'green'], '{bar}')} | ${label} {percentage}% | {value}/{total}`; + + return new cliProgress.SingleBar({ + format, + barCompleteChar: '\u2588', + barIncompleteChar: '\u2591', + hideCursor: true, + }); +} + +/** + * + * @param {import('cli-progress').SingleBar} bar + * @param {number} total + */ +export function startProgressBar(bar, total) { + bar.start(total, 0); +} + +/** + * @param {import('cli-progress').SingleBar} bar + * @param {number} value + */ +export function updateProgressBar(bar, value = 1) { + bar.update(value); +} + +/** + * @param {import('cli-progress').SingleBar} bar + */ +export function stopProgressBar(bar) { + bar.stop(); +} From a3e762355976f6d687498b2c66c1726b9bc74d31 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Wed, 11 Sep 2024 11:04:15 +0200 Subject: [PATCH 2/9] test: progress-bar --- src/utils/tests/progressBar.test.mjs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/utils/tests/progressBar.test.mjs diff --git a/src/utils/tests/progressBar.test.mjs b/src/utils/tests/progressBar.test.mjs new file mode 100644 index 0000000..940cbc5 --- /dev/null +++ b/src/utils/tests/progressBar.test.mjs @@ -0,0 +1,30 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; + +import cliProgress from 'cli-progress'; + +import { + createProgressBar, + startProgressBar, + stopProgressBar, +} from '../progressBar.mjs'; + +describe('progressBar', () => { + it('createProgressBar returns an instance of SingleBar', () => { + const bar = createProgressBar(); + assert.ok(bar instanceof cliProgress.SingleBar); + }); + + it('startProgressBar sets the correct total value', () => { + const bar = createProgressBar(); + startProgressBar(bar, 100); + assert.strictEqual(bar.getTotal(), 100); + }); + + it('stopProgressBar stops the progress bar', () => { + const bar = createProgressBar(); + startProgressBar(bar, 100); + stopProgressBar(bar); + assert.strictEqual(bar.isActive, false); + }); +}); From b96cfd025e95b72cf758e6225d4621e799f1d562 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Thu, 12 Sep 2024 09:05:59 +0200 Subject: [PATCH 3/9] fix: code style Co-authored-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- src/loader.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/loader.mjs b/src/loader.mjs index bb0062a..505b6ee 100644 --- a/src/loader.mjs +++ b/src/loader.mjs @@ -34,6 +34,7 @@ const createLoader = () => { ); const progressBar = createProgressBar('Loading files'); + startProgressBar(progressBar, resolvedFiles.length); return resolvedFiles.map(async filePath => { From 9180963254bd2cbcab14eae4f18690059b6ac212 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Thu, 12 Sep 2024 09:06:33 +0200 Subject: [PATCH 4/9] fix: code style Co-authored-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- src/loader.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/loader.mjs b/src/loader.mjs index 505b6ee..18f07c1 100644 --- a/src/loader.mjs +++ b/src/loader.mjs @@ -42,7 +42,9 @@ const createLoader = () => { updateProgressBar(progressBar); // normally we stop the progress bar when the loop is done // but here we return the loop so we need to stop it when the last file is loaded - if (progressBar.value === progressBar.total) stopProgressBar(progressBar); + if (progressBar.value === progressBar.total) { + stopProgressBar(progressBar); + } return new VFile({ path: filePath, value: fileContents }); }); From 3d1760fcd50fe53368eb24887bdad9aa5ac369dd Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Thu, 12 Sep 2024 09:06:44 +0200 Subject: [PATCH 5/9] fix: code style Co-authored-by: flakey5 <73616808+flakey5@users.noreply.github.com> --- src/generators/legacy-html/index.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index 07f7889..3affe9d 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -169,6 +169,7 @@ export default { await writeFile(join(output, `${node.api}.html`), minified); updateProgressBar(progressBar); } + // Stops the progress bar and clears the line stopProgressBar(progressBar); From 5e879ff086b2de25bebd851edac1330befea0111 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Mon, 14 Oct 2024 21:26:49 +0200 Subject: [PATCH 6/9] fix + update --- src/generators/json-simple/index.mjs | 9 ++++++++ src/generators/legacy-html/index.mjs | 13 ++++------- src/loader.mjs | 14 ++++-------- src/utils/progressBar.mjs | 24 -------------------- src/utils/tests/progressBar.test.mjs | 33 ++++++++-------------------- 5 files changed, 26 insertions(+), 67 deletions(-) diff --git a/src/generators/json-simple/index.mjs b/src/generators/json-simple/index.mjs index 938e190..84abaf0 100644 --- a/src/generators/json-simple/index.mjs +++ b/src/generators/json-simple/index.mjs @@ -7,6 +7,7 @@ import { remove } from 'unist-util-remove'; import createQueries from '../../queries.mjs'; import { getRemark } from '../../utils/remark.mjs'; +import { createProgressBar } from '../../utils/progressBar.mjs'; /** * This generator generates a simplified JSON version of the API docs and returns it as a string @@ -33,6 +34,10 @@ export default { // Gets a remark processor for stringifying the AST tree into JSON const remarkProcessor = getRemark(); + // Creates a progress bar for the JSON generation + const progressBar = createProgressBar('Generating JSON'); + progressBar.start(input.length, 0); + // Iterates the input (ApiDocMetadataEntry) and performs a few changes const mappedInput = input.map(node => { // Deep clones the content nodes to avoid affecting upstream nodes @@ -48,9 +53,13 @@ export default { // For the JSON generate we want to transform the whole content into JSON content.toJSON = () => remarkProcessor.stringify(content); + progressBar.increment(); + return { ...node, content }; }); + progressBar.stop(); + // This simply grabs all the different files and stringifies them const stringifiedContent = JSON.stringify(mappedInput); diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index 3affe9d..16b4aae 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -11,12 +11,7 @@ import tableOfContents from './utils/tableOfContents.mjs'; import { groupNodesByModule } from '../../utils/generators.mjs'; import { getRemarkRehype } from '../../utils/remark.mjs'; -import { - createProgressBar, - startProgressBar, - updateProgressBar, - stopProgressBar, -} from '../../utils/progressBar.mjs'; +import { createProgressBar } from '../../utils/progressBar.mjs'; /** * @typedef {{ @@ -155,7 +150,7 @@ export default { // Creates a progress bar to show the progress of the generation process const progressBar = createProgressBar('Generating HTML files'); - startProgressBar(progressBar, headNodes.length); + progressBar.start(headNodes.length, 0); for (const node of headNodes) { const result = processModuleNodes(node); @@ -167,11 +162,11 @@ export default { }); await writeFile(join(output, `${node.api}.html`), minified); - updateProgressBar(progressBar); + progressBar.increment(); } // Stops the progress bar and clears the line - stopProgressBar(progressBar); + progressBar.stop(); // Define the output folder for API docs assets const assetsFolder = join(output, 'assets'); diff --git a/src/loader.mjs b/src/loader.mjs index 18f07c1..3bedc24 100644 --- a/src/loader.mjs +++ b/src/loader.mjs @@ -6,12 +6,7 @@ import { extname } from 'node:path'; import { globSync } from 'glob'; import { VFile } from 'vfile'; -import { - createProgressBar, - startProgressBar, - updateProgressBar, - stopProgressBar, -} from './utils/progressBar.mjs'; +import { createProgressBar } from './utils/progressBar.mjs'; /** * This method creates a simple abstract "Loader", which technically @@ -34,16 +29,15 @@ const createLoader = () => { ); const progressBar = createProgressBar('Loading files'); - - startProgressBar(progressBar, resolvedFiles.length); + progressBar.start(resolvedFiles.length, 0); return resolvedFiles.map(async filePath => { const fileContents = await readFile(filePath, 'utf-8'); - updateProgressBar(progressBar); + progressBar.increment(); // normally we stop the progress bar when the loop is done // but here we return the loop so we need to stop it when the last file is loaded if (progressBar.value === progressBar.total) { - stopProgressBar(progressBar); + progressBar.stop(); } return new VFile({ path: filePath, value: fileContents }); diff --git a/src/utils/progressBar.mjs b/src/utils/progressBar.mjs index 8b1637a..8b55763 100644 --- a/src/utils/progressBar.mjs +++ b/src/utils/progressBar.mjs @@ -17,27 +17,3 @@ export function createProgressBar(label = '') { hideCursor: true, }); } - -/** - * - * @param {import('cli-progress').SingleBar} bar - * @param {number} total - */ -export function startProgressBar(bar, total) { - bar.start(total, 0); -} - -/** - * @param {import('cli-progress').SingleBar} bar - * @param {number} value - */ -export function updateProgressBar(bar, value = 1) { - bar.update(value); -} - -/** - * @param {import('cli-progress').SingleBar} bar - */ -export function stopProgressBar(bar) { - bar.stop(); -} diff --git a/src/utils/tests/progressBar.test.mjs b/src/utils/tests/progressBar.test.mjs index 940cbc5..6264cc8 100644 --- a/src/utils/tests/progressBar.test.mjs +++ b/src/utils/tests/progressBar.test.mjs @@ -1,30 +1,15 @@ import assert from 'node:assert'; -import { describe, it } from 'node:test'; +import { test } from 'node:test'; import cliProgress from 'cli-progress'; -import { - createProgressBar, - startProgressBar, - stopProgressBar, -} from '../progressBar.mjs'; +import { createProgressBar } from '../progressBar.mjs'; -describe('progressBar', () => { - it('createProgressBar returns an instance of SingleBar', () => { - const bar = createProgressBar(); - assert.ok(bar instanceof cliProgress.SingleBar); - }); - - it('startProgressBar sets the correct total value', () => { - const bar = createProgressBar(); - startProgressBar(bar, 100); - assert.strictEqual(bar.getTotal(), 100); - }); - - it('stopProgressBar stops the progress bar', () => { - const bar = createProgressBar(); - startProgressBar(bar, 100); - stopProgressBar(bar); - assert.strictEqual(bar.isActive, false); - }); +/** + * Simple test to unsure that the progress bar is created + * and we asume that it's work with ou style + */ +test('createProgressBar returns an instance of SingleBar', () => { + const bar = createProgressBar(); + assert.ok(bar instanceof cliProgress.SingleBar); }); From 6297b0d667c85779911e6d8206194e5dc898b249 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Mon, 14 Oct 2024 21:28:17 +0200 Subject: [PATCH 7/9] fix: codespell --- src/utils/tests/progressBar.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/tests/progressBar.test.mjs b/src/utils/tests/progressBar.test.mjs index 6264cc8..ee8320d 100644 --- a/src/utils/tests/progressBar.test.mjs +++ b/src/utils/tests/progressBar.test.mjs @@ -7,7 +7,7 @@ import { createProgressBar } from '../progressBar.mjs'; /** * Simple test to unsure that the progress bar is created - * and we asume that it's work with ou style + * and we assume that it's work with ou style */ test('createProgressBar returns an instance of SingleBar', () => { const bar = createProgressBar(); From 9e75a18ccb4433b21155038dc7b9363283e035e8 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Tue, 22 Oct 2024 10:18:00 +0200 Subject: [PATCH 8/9] wip --- fixtures/addons.md | 1387 ++++++++++++++ fixtures/assert.md | 2587 ++++++++++++++++++++++++++ fixtures/async_context.md | 888 +++++++++ src/generators/json-simple/index.mjs | 9 - src/generators/legacy-html/index.mjs | 9 - src/loader.mjs | 2 +- src/parser.mjs | 14 +- src/utils/generators.mjs | 8 + src/utils/progressBar.mjs | 7 +- src/utils/tests/progressBar.test.mjs | 2 +- 10 files changed, 4891 insertions(+), 22 deletions(-) create mode 100644 fixtures/addons.md create mode 100644 fixtures/assert.md create mode 100644 fixtures/async_context.md diff --git a/fixtures/addons.md b/fixtures/addons.md new file mode 100644 index 0000000..446a63b --- /dev/null +++ b/fixtures/addons.md @@ -0,0 +1,1387 @@ +# C++ addons + + + + + +_Addons_ are dynamically-linked shared objects written in C++. The +[`require()`][require] function can load addons as ordinary Node.js modules. +Addons provide an interface between JavaScript and C/C++ libraries. + +There are three options for implementing addons: Node-API, nan, or direct +use of internal V8, libuv, and Node.js libraries. Unless there is a need for +direct access to functionality which is not exposed by Node-API, use Node-API. +Refer to [C/C++ addons with Node-API](n-api.md) for more information on +Node-API. + +When not using Node-API, implementing addons is complicated, +involving knowledge of several components and APIs: + +* [V8][]: the C++ library Node.js uses to provide the + JavaScript implementation. V8 provides the mechanisms for creating objects, + calling functions, etc. V8's API is documented mostly in the + `v8.h` header file (`deps/v8/include/v8.h` in the Node.js source + tree), which is also available [online][v8-docs]. + +* [libuv][]: The C library that implements the Node.js event loop, its worker + threads and all of the asynchronous behaviors of the platform. It also + serves as a cross-platform abstraction library, giving easy, POSIX-like + access across all major operating systems to many common system tasks, such + as interacting with the file system, sockets, timers, and system events. libuv + also provides a threading abstraction similar to POSIX threads for + more sophisticated asynchronous addons that need to move beyond the + standard event loop. Addon authors should + avoid blocking the event loop with I/O or other time-intensive tasks by + offloading work via libuv to non-blocking system operations, worker threads, + or a custom use of libuv threads. + +* Internal Node.js libraries. Node.js itself exports C++ APIs that addons can + use, the most important of which is the `node::ObjectWrap` class. + +* Node.js includes other statically linked libraries including OpenSSL. These + other libraries are located in the `deps/` directory in the Node.js source + tree. Only the libuv, OpenSSL, V8, and zlib symbols are purposefully + re-exported by Node.js and may be used to various extents by addons. See + [Linking to libraries included with Node.js][] for additional information. + +All of the following examples are available for [download][] and may +be used as the starting-point for an addon. + +## Hello world + +This "Hello world" example is a simple addon, written in C++, that is the +equivalent of the following JavaScript code: + +```js +module.exports.hello = () => 'world'; +``` + +First, create the file `hello.cc`: + +```cpp +// hello.cc +#include + +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void Method(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + args.GetReturnValue().Set(String::NewFromUtf8( + isolate, "world").ToLocalChecked()); +} + +void Initialize(Local exports) { + NODE_SET_METHOD(exports, "hello", Method); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) + +} // namespace demo +``` + +All Node.js addons must export an initialization function following +the pattern: + +```cpp +void Initialize(Local exports); +NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) +``` + +There is no semi-colon after `NODE_MODULE` as it's not a function (see +`node.h`). + +The `module_name` must match the filename of the final binary (excluding +the `.node` suffix). + +In the `hello.cc` example, then, the initialization function is `Initialize` +and the addon module name is `addon`. + +When building addons with `node-gyp`, using the macro `NODE_GYP_MODULE_NAME` as +the first parameter of `NODE_MODULE()` will ensure that the name of the final +binary will be passed to `NODE_MODULE()`. + +Addons defined with `NODE_MODULE()` can not be loaded in multiple contexts or +multiple threads at the same time. + +### Context-aware addons + +There are environments in which Node.js addons may need to be loaded multiple +times in multiple contexts. For example, the [Electron][] runtime runs multiple +instances of Node.js in a single process. Each instance will have its own +`require()` cache, and thus each instance will need a native addon to behave +correctly when loaded via `require()`. This means that the addon +must support multiple initializations. + +A context-aware addon can be constructed by using the macro +`NODE_MODULE_INITIALIZER`, which expands to the name of a function which Node.js +will expect to find when it loads an addon. An addon can thus be initialized as +in the following example: + +```cpp +using namespace v8; + +extern "C" NODE_MODULE_EXPORT void +NODE_MODULE_INITIALIZER(Local exports, + Local module, + Local context) { + /* Perform addon initialization steps here. */ +} +``` + +Another option is to use the macro `NODE_MODULE_INIT()`, which will also +construct a context-aware addon. Unlike `NODE_MODULE()`, which is used to +construct an addon around a given addon initializer function, +`NODE_MODULE_INIT()` serves as the declaration of such an initializer to be +followed by a function body. + +The following three variables may be used inside the function body following an +invocation of `NODE_MODULE_INIT()`: + +* `Local exports`, +* `Local module`, and +* `Local context` + +The choice to build a context-aware addon carries with it the responsibility of +carefully managing global static data. Since the addon may be loaded multiple +times, potentially even from different threads, any global static data stored +in the addon must be properly protected, and must not contain any persistent +references to JavaScript objects. The reason for this is that JavaScript +objects are only valid in one context, and will likely cause a crash when +accessed from the wrong context or from a different thread than the one on which +they were created. + +The context-aware addon can be structured to avoid global static data by +performing the following steps: + +* Define a class which will hold per-addon-instance data and which has a static + member of the form + ```cpp + static void DeleteInstance(void* data) { + // Cast `data` to an instance of the class and delete it. + } + ``` +* Heap-allocate an instance of this class in the addon initializer. This can be + accomplished using the `new` keyword. +* Call `node::AddEnvironmentCleanupHook()`, passing it the above-created + instance and a pointer to `DeleteInstance()`. This will ensure the instance is + deleted when the environment is torn down. +* Store the instance of the class in a `v8::External`, and +* Pass the `v8::External` to all methods exposed to JavaScript by passing it + to `v8::FunctionTemplate::New()` or `v8::Function::New()` which creates the + native-backed JavaScript functions. The third parameter of + `v8::FunctionTemplate::New()` or `v8::Function::New()` accepts the + `v8::External` and makes it available in the native callback using the + `v8::FunctionCallbackInfo::Data()` method. + +This will ensure that the per-addon-instance data reaches each binding that can +be called from JavaScript. The per-addon-instance data must also be passed into +any asynchronous callbacks the addon may create. + +The following example illustrates the implementation of a context-aware addon: + +```cpp +#include + +using namespace v8; + +class AddonData { + public: + explicit AddonData(Isolate* isolate): + call_count(0) { + // Ensure this per-addon-instance data is deleted at environment cleanup. + node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this); + } + + // Per-addon data. + int call_count; + + static void DeleteInstance(void* data) { + delete static_cast(data); + } +}; + +static void Method(const v8::FunctionCallbackInfo& info) { + // Retrieve the per-addon-instance data. + AddonData* data = + reinterpret_cast(info.Data().As()->Value()); + data->call_count++; + info.GetReturnValue().Set((double)data->call_count); +} + +// Initialize this addon to be context-aware. +NODE_MODULE_INIT(/* exports, module, context */) { + Isolate* isolate = context->GetIsolate(); + + // Create a new instance of `AddonData` for this instance of the addon and + // tie its life cycle to that of the Node.js environment. + AddonData* data = new AddonData(isolate); + + // Wrap the data in a `v8::External` so we can pass it to the method we + // expose. + Local external = External::New(isolate, data); + + // Expose the method `Method` to JavaScript, and make sure it receives the + // per-addon-instance data we created above by passing `external` as the + // third parameter to the `FunctionTemplate` constructor. + exports->Set(context, + String::NewFromUtf8(isolate, "method").ToLocalChecked(), + FunctionTemplate::New(isolate, Method, external) + ->GetFunction(context).ToLocalChecked()).FromJust(); +} +``` + +#### Worker support + + + +In order to be loaded from multiple Node.js environments, +such as a main thread and a Worker thread, an add-on needs to either: + +* Be an Node-API addon, or +* Be declared as context-aware using `NODE_MODULE_INIT()` as described above + +In order to support [`Worker`][] threads, addons need to clean up any resources +they may have allocated when such a thread exists. This can be achieved through +the usage of the `AddEnvironmentCleanupHook()` function: + +```cpp +void AddEnvironmentCleanupHook(v8::Isolate* isolate, + void (*fun)(void* arg), + void* arg); +``` + +This function adds a hook that will run before a given Node.js instance shuts +down. If necessary, such hooks can be removed before they are run using +`RemoveEnvironmentCleanupHook()`, which has the same signature. Callbacks are +run in last-in first-out order. + +If necessary, there is an additional pair of `AddEnvironmentCleanupHook()` +and `RemoveEnvironmentCleanupHook()` overloads, where the cleanup hook takes a +callback function. This can be used for shutting down asynchronous resources, +such as any libuv handles registered by the addon. + +The following `addon.cc` uses `AddEnvironmentCleanupHook`: + +```cpp +// addon.cc +#include +#include +#include + +using node::AddEnvironmentCleanupHook; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; + +// Note: In a real-world application, do not rely on static/global data. +static char cookie[] = "yum yum"; +static int cleanup_cb1_called = 0; +static int cleanup_cb2_called = 0; + +static void cleanup_cb1(void* arg) { + Isolate* isolate = static_cast(arg); + HandleScope scope(isolate); + Local obj = Object::New(isolate); + assert(!obj.IsEmpty()); // assert VM is still alive + assert(obj->IsObject()); + cleanup_cb1_called++; +} + +static void cleanup_cb2(void* arg) { + assert(arg == static_cast(cookie)); + cleanup_cb2_called++; +} + +static void sanity_check(void*) { + assert(cleanup_cb1_called == 1); + assert(cleanup_cb2_called == 1); +} + +// Initialize this addon to be context-aware. +NODE_MODULE_INIT(/* exports, module, context */) { + Isolate* isolate = context->GetIsolate(); + + AddEnvironmentCleanupHook(isolate, sanity_check, nullptr); + AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie); + AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate); +} +``` + +Test in JavaScript by running: + +```js +// test.js +require('./build/Release/addon'); +``` + +### Building + +Once the source code has been written, it must be compiled into the binary +`addon.node` file. To do so, create a file called `binding.gyp` in the +top-level of the project describing the build configuration of the module +using a JSON-like format. This file is used by [node-gyp][], a tool written +specifically to compile Node.js addons. + +```json +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "hello.cc" ] + } + ] +} +``` + +A version of the `node-gyp` utility is bundled and distributed with +Node.js as part of `npm`. This version is not made directly available for +developers to use and is intended only to support the ability to use the +`npm install` command to compile and install addons. Developers who wish to +use `node-gyp` directly can install it using the command +`npm install -g node-gyp`. See the `node-gyp` [installation instructions][] for +more information, including platform-specific requirements. + +Once the `binding.gyp` file has been created, use `node-gyp configure` to +generate the appropriate project build files for the current platform. This +will generate either a `Makefile` (on Unix platforms) or a `vcxproj` file +(on Windows) in the `build/` directory. + +Next, invoke the `node-gyp build` command to generate the compiled `addon.node` +file. This will be put into the `build/Release/` directory. + +When using `npm install` to install a Node.js addon, npm uses its own bundled +version of `node-gyp` to perform this same set of actions, generating a +compiled version of the addon for the user's platform on demand. + +Once built, the binary addon can be used from within Node.js by pointing +[`require()`][require] to the built `addon.node` module: + +```js +// hello.js +const addon = require('./build/Release/addon'); + +console.log(addon.hello()); +// Prints: 'world' +``` + +Because the exact path to the compiled addon binary can vary depending on how +it is compiled (i.e. sometimes it may be in `./build/Debug/`), addons can use +the [bindings][] package to load the compiled module. + +While the `bindings` package implementation is more sophisticated in how it +locates addon modules, it is essentially using a `try…catch` pattern similar to: + +```js +try { + return require('./build/Release/addon.node'); +} catch (err) { + return require('./build/Debug/addon.node'); +} +``` + +### Linking to libraries included with Node.js + +Node.js uses statically linked libraries such as V8, libuv, and OpenSSL. All +addons are required to link to V8 and may link to any of the other dependencies +as well. Typically, this is as simple as including the appropriate +`#include <...>` statements (e.g. `#include `) and `node-gyp` will locate +the appropriate headers automatically. However, there are a few caveats to be +aware of: + +* When `node-gyp` runs, it will detect the specific release version of Node.js + and download either the full source tarball or just the headers. If the full + source is downloaded, addons will have complete access to the full set of + Node.js dependencies. However, if only the Node.js headers are downloaded, + then only the symbols exported by Node.js will be available. + +* `node-gyp` can be run using the `--nodedir` flag pointing at a local Node.js + source image. Using this option, the addon will have access to the full set of + dependencies. + +### Loading addons using `require()` + +The filename extension of the compiled addon binary is `.node` (as opposed +to `.dll` or `.so`). The [`require()`][require] function is written to look for +files with the `.node` file extension and initialize those as dynamically-linked +libraries. + +When calling [`require()`][require], the `.node` extension can usually be +omitted and Node.js will still find and initialize the addon. One caveat, +however, is that Node.js will first attempt to locate and load modules or +JavaScript files that happen to share the same base name. For instance, if +there is a file `addon.js` in the same directory as the binary `addon.node`, +then [`require('addon')`][require] will give precedence to the `addon.js` file +and load it instead. + +## Native abstractions for Node.js + +Each of the examples illustrated in this document directly use the +Node.js and V8 APIs for implementing addons. The V8 API can, and has, changed +dramatically from one V8 release to the next (and one major Node.js release to +the next). With each change, addons may need to be updated and recompiled in +order to continue functioning. The Node.js release schedule is designed to +minimize the frequency and impact of such changes but there is little that +Node.js can do to ensure stability of the V8 APIs. + +The [Native Abstractions for Node.js][] (or `nan`) provide a set of tools that +addon developers are recommended to use to keep compatibility between past and +future releases of V8 and Node.js. See the `nan` [examples][] for an +illustration of how it can be used. + +## Node-API + +> Stability: 2 - Stable + +Node-API is an API for building native addons. It is independent from +the underlying JavaScript runtime (e.g. V8) and is maintained as part of +Node.js itself. This API will be Application Binary Interface (ABI) stable +across versions of Node.js. It is intended to insulate addons from +changes in the underlying JavaScript engine and allow modules +compiled for one version to run on later versions of Node.js without +recompilation. Addons are built/packaged with the same approach/tools +outlined in this document (node-gyp, etc.). The only difference is the +set of APIs that are used by the native code. Instead of using the V8 +or [Native Abstractions for Node.js][] APIs, the functions available +in the Node-API are used. + +Creating and maintaining an addon that benefits from the ABI stability +provided by Node-API carries with it certain +[implementation considerations][]. + +To use Node-API in the above "Hello world" example, replace the content of +`hello.cc` with the following. All other instructions remain the same. + +```cpp +// hello.cc using Node-API +#include + +namespace demo { + +napi_value Method(napi_env env, napi_callback_info args) { + napi_value greeting; + napi_status status; + + status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting); + if (status != napi_ok) return nullptr; + return greeting; +} + +napi_value init(napi_env env, napi_value exports) { + napi_status status; + napi_value fn; + + status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn); + if (status != napi_ok) return nullptr; + + status = napi_set_named_property(env, exports, "hello", fn); + if (status != napi_ok) return nullptr; + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, init) + +} // namespace demo +``` + +The functions available and how to use them are documented in +[C/C++ addons with Node-API](n-api.md). + +## Addon examples + +Following are some example addons intended to help developers get started. The +examples use the V8 APIs. Refer to the online [V8 reference][v8-docs] +for help with the various V8 calls, and V8's [Embedder's Guide][] for an +explanation of several concepts used such as handles, scopes, function +templates, etc. + +Each of these examples using the following `binding.gyp` file: + +```json +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc" ] + } + ] +} +``` + +In cases where there is more than one `.cc` file, simply add the additional +filename to the `sources` array: + +```json +"sources": ["addon.cc", "myexample.cc"] +``` + +Once the `binding.gyp` file is ready, the example addons can be configured and +built using `node-gyp`: + +```bash +node-gyp configure build +``` + +### Function arguments + +Addons will typically expose objects and functions that can be accessed from +JavaScript running within Node.js. When functions are invoked from JavaScript, +the input arguments and return value must be mapped to and from the C/C++ +code. + +The following example illustrates how to read function arguments passed from +JavaScript and how to return a result: + +```cpp +// addon.cc +#include + +namespace demo { + +using v8::Exception; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; + +// This is the implementation of the "add" method +// Input arguments are passed using the +// const FunctionCallbackInfo& args struct +void Add(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + // Check the number of arguments passed. + if (args.Length() < 2) { + // Throw an Error that is passed back to JavaScript + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, + "Wrong number of arguments").ToLocalChecked())); + return; + } + + // Check the argument types + if (!args[0]->IsNumber() || !args[1]->IsNumber()) { + isolate->ThrowException(Exception::TypeError( + String::NewFromUtf8(isolate, + "Wrong arguments").ToLocalChecked())); + return; + } + + // Perform the operation + double value = + args[0].As()->Value() + args[1].As()->Value(); + Local num = Number::New(isolate, value); + + // Set the return value (using the passed in + // FunctionCallbackInfo&) + args.GetReturnValue().Set(num); +} + +void Init(Local exports) { + NODE_SET_METHOD(exports, "add", Add); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Init) + +} // namespace demo +``` + +Once compiled, the example addon can be required and used from within Node.js: + +```js +// test.js +const addon = require('./build/Release/addon'); + +console.log('This should be eight:', addon.add(3, 5)); +``` + +### Callbacks + +It is common practice within addons to pass JavaScript functions to a C++ +function and execute them from there. The following example illustrates how +to invoke such callbacks: + +```cpp +// addon.cc +#include + +namespace demo { + +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Null; +using v8::Object; +using v8::String; +using v8::Value; + +void RunCallback(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Local cb = Local::Cast(args[0]); + const unsigned argc = 1; + Local argv[argc] = { + String::NewFromUtf8(isolate, + "hello world").ToLocalChecked() }; + cb->Call(context, Null(isolate), argc, argv).ToLocalChecked(); +} + +void Init(Local exports, Local module) { + NODE_SET_METHOD(module, "exports", RunCallback); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Init) + +} // namespace demo +``` + +This example uses a two-argument form of `Init()` that receives the full +`module` object as the second argument. This allows the addon to completely +overwrite `exports` with a single function instead of adding the function as a +property of `exports`. + +To test it, run the following JavaScript: + +```js +// test.js +const addon = require('./build/Release/addon'); + +addon((msg) => { + console.log(msg); +// Prints: 'hello world' +}); +``` + +In this example, the callback function is invoked synchronously. + +### Object factory + +Addons can create and return new objects from within a C++ function as +illustrated in the following example. An object is created and returned with a +property `msg` that echoes the string passed to `createObject()`: + +```cpp +// addon.cc +#include + +namespace demo { + +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void CreateObject(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + Local obj = Object::New(isolate); + obj->Set(context, + String::NewFromUtf8(isolate, + "msg").ToLocalChecked(), + args[0]->ToString(context).ToLocalChecked()) + .FromJust(); + + args.GetReturnValue().Set(obj); +} + +void Init(Local exports, Local module) { + NODE_SET_METHOD(module, "exports", CreateObject); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Init) + +} // namespace demo +``` + +To test it in JavaScript: + +```js +// test.js +const addon = require('./build/Release/addon'); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +console.log(obj1.msg, obj2.msg); +// Prints: 'hello world' +``` + +### Function factory + +Another common scenario is creating JavaScript functions that wrap C++ +functions and returning those back to JavaScript: + +```cpp +// addon.cc +#include + +namespace demo { + +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void MyFunction(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + args.GetReturnValue().Set(String::NewFromUtf8( + isolate, "hello world").ToLocalChecked()); +} + +void CreateFunction(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + Local context = isolate->GetCurrentContext(); + Local tpl = FunctionTemplate::New(isolate, MyFunction); + Local fn = tpl->GetFunction(context).ToLocalChecked(); + + // omit this to make it anonymous + fn->SetName(String::NewFromUtf8( + isolate, "theFunction").ToLocalChecked()); + + args.GetReturnValue().Set(fn); +} + +void Init(Local exports, Local module) { + NODE_SET_METHOD(module, "exports", CreateFunction); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Init) + +} // namespace demo +``` + +To test: + +```js +// test.js +const addon = require('./build/Release/addon'); + +const fn = addon(); +console.log(fn()); +// Prints: 'hello world' +``` + +### Wrapping C++ objects + +It is also possible to wrap C++ objects/classes in a way that allows new +instances to be created using the JavaScript `new` operator: + +```cpp +// addon.cc +#include +#include "myobject.h" + +namespace demo { + +using v8::Local; +using v8::Object; + +void InitAll(Local exports) { + MyObject::Init(exports); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll) + +} // namespace demo +``` + +Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`: + +```cpp +// myobject.h +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include +#include + +namespace demo { + +class MyObject : public node::ObjectWrap { + public: + static void Init(v8::Local exports); + + private: + explicit MyObject(double value = 0); + ~MyObject(); + + static void New(const v8::FunctionCallbackInfo& args); + static void PlusOne(const v8::FunctionCallbackInfo& args); + + double value_; +}; + +} // namespace demo + +#endif +``` + +In `myobject.cc`, implement the various methods that are to be exposed. +In the following code, the method `plusOne()` is exposed by adding it to the +constructor's prototype: + +```cpp +// myobject.cc +#include "myobject.h" + +namespace demo { + +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::ObjectTemplate; +using v8::String; +using v8::Value; + +MyObject::MyObject(double value) : value_(value) { +} + +MyObject::~MyObject() { +} + +void MyObject::Init(Local exports) { + Isolate* isolate = exports->GetIsolate(); + Local context = isolate->GetCurrentContext(); + + Local addon_data_tpl = ObjectTemplate::New(isolate); + addon_data_tpl->SetInternalFieldCount(1); // 1 field for the MyObject::New() + Local addon_data = + addon_data_tpl->NewInstance(context).ToLocalChecked(); + + // Prepare constructor template + Local tpl = FunctionTemplate::New(isolate, New, addon_data); + tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + // Prototype + NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); + + Local constructor = tpl->GetFunction(context).ToLocalChecked(); + addon_data->SetInternalField(0, constructor); + exports->Set(context, String::NewFromUtf8( + isolate, "MyObject").ToLocalChecked(), + constructor).FromJust(); +} + +void MyObject::New(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.IsConstructCall()) { + // Invoked as constructor: `new MyObject(...)` + double value = args[0]->IsUndefined() ? + 0 : args[0]->NumberValue(context).FromMaybe(0); + MyObject* obj = new MyObject(value); + obj->Wrap(args.This()); + args.GetReturnValue().Set(args.This()); + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + const int argc = 1; + Local argv[argc] = { args[0] }; + Local cons = + args.Data().As()->GetInternalField(0) + .As().As(); + Local result = + cons->NewInstance(context, argc, argv).ToLocalChecked(); + args.GetReturnValue().Set(result); + } +} + +void MyObject::PlusOne(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + MyObject* obj = ObjectWrap::Unwrap(args.This()); + obj->value_ += 1; + + args.GetReturnValue().Set(Number::New(isolate, obj->value_)); +} + +} // namespace demo +``` + +To build this example, the `myobject.cc` file must be added to the +`binding.gyp`: + +```json +{ + "targets": [ + { + "target_name": "addon", + "sources": [ + "addon.cc", + "myobject.cc" + ] + } + ] +} +``` + +Test it with: + +```js +// test.js +const addon = require('./build/Release/addon'); + +const obj = new addon.MyObject(10); +console.log(obj.plusOne()); +// Prints: 11 +console.log(obj.plusOne()); +// Prints: 12 +console.log(obj.plusOne()); +// Prints: 13 +``` + +The destructor for a wrapper object will run when the object is +garbage-collected. For destructor testing, there are command-line flags that +can be used to make it possible to force garbage collection. These flags are +provided by the underlying V8 JavaScript engine. They are subject to change +or removal at any time. They are not documented by Node.js or V8, and they +should never be used outside of testing. + +During shutdown of the process or worker threads destructors are not called +by the JS engine. Therefore it's the responsibility of the user to track +these objects and ensure proper destruction to avoid resource leaks. + +### Factory of wrapped objects + +Alternatively, it is possible to use a factory pattern to avoid explicitly +creating object instances using the JavaScript `new` operator: + +```js +const obj = addon.createObject(); +// instead of: +// const obj = new addon.Object(); +``` + +First, the `createObject()` method is implemented in `addon.cc`: + +```cpp +// addon.cc +#include +#include "myobject.h" + +namespace demo { + +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +void CreateObject(const FunctionCallbackInfo& args) { + MyObject::NewInstance(args); +} + +void InitAll(Local exports, Local module) { + MyObject::Init(exports->GetIsolate()); + + NODE_SET_METHOD(module, "exports", CreateObject); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll) + +} // namespace demo +``` + +In `myobject.h`, the static method `NewInstance()` is added to handle +instantiating the object. This method takes the place of using `new` in +JavaScript: + +```cpp +// myobject.h +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include +#include + +namespace demo { + +class MyObject : public node::ObjectWrap { + public: + static void Init(v8::Isolate* isolate); + static void NewInstance(const v8::FunctionCallbackInfo& args); + + private: + explicit MyObject(double value = 0); + ~MyObject(); + + static void New(const v8::FunctionCallbackInfo& args); + static void PlusOne(const v8::FunctionCallbackInfo& args); + static v8::Global constructor; + double value_; +}; + +} // namespace demo + +#endif +``` + +The implementation in `myobject.cc` is similar to the previous example: + +```cpp +// myobject.cc +#include +#include "myobject.h" + +namespace demo { + +using node::AddEnvironmentCleanupHook; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Global; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; + +// Warning! This is not thread-safe, this addon cannot be used for worker +// threads. +Global MyObject::constructor; + +MyObject::MyObject(double value) : value_(value) { +} + +MyObject::~MyObject() { +} + +void MyObject::Init(Isolate* isolate) { + // Prepare constructor template + Local tpl = FunctionTemplate::New(isolate, New); + tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + // Prototype + NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); + + Local context = isolate->GetCurrentContext(); + constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked()); + + AddEnvironmentCleanupHook(isolate, [](void*) { + constructor.Reset(); + }, nullptr); +} + +void MyObject::New(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.IsConstructCall()) { + // Invoked as constructor: `new MyObject(...)` + double value = args[0]->IsUndefined() ? + 0 : args[0]->NumberValue(context).FromMaybe(0); + MyObject* obj = new MyObject(value); + obj->Wrap(args.This()); + args.GetReturnValue().Set(args.This()); + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + const int argc = 1; + Local argv[argc] = { args[0] }; + Local cons = Local::New(isolate, constructor); + Local instance = + cons->NewInstance(context, argc, argv).ToLocalChecked(); + args.GetReturnValue().Set(instance); + } +} + +void MyObject::NewInstance(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + const unsigned argc = 1; + Local argv[argc] = { args[0] }; + Local cons = Local::New(isolate, constructor); + Local context = isolate->GetCurrentContext(); + Local instance = + cons->NewInstance(context, argc, argv).ToLocalChecked(); + + args.GetReturnValue().Set(instance); +} + +void MyObject::PlusOne(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + MyObject* obj = ObjectWrap::Unwrap(args.This()); + obj->value_ += 1; + + args.GetReturnValue().Set(Number::New(isolate, obj->value_)); +} + +} // namespace demo +``` + +Once again, to build this example, the `myobject.cc` file must be added to the +`binding.gyp`: + +```json +{ + "targets": [ + { + "target_name": "addon", + "sources": [ + "addon.cc", + "myobject.cc" + ] + } + ] +} +``` + +Test it with: + +```js +// test.js +const createObject = require('./build/Release/addon'); + +const obj = createObject(10); +console.log(obj.plusOne()); +// Prints: 11 +console.log(obj.plusOne()); +// Prints: 12 +console.log(obj.plusOne()); +// Prints: 13 + +const obj2 = createObject(20); +console.log(obj2.plusOne()); +// Prints: 21 +console.log(obj2.plusOne()); +// Prints: 22 +console.log(obj2.plusOne()); +// Prints: 23 +``` + +### Passing wrapped objects around + +In addition to wrapping and returning C++ objects, it is possible to pass +wrapped objects around by unwrapping them with the Node.js helper function +`node::ObjectWrap::Unwrap`. The following examples shows a function `add()` +that can take two `MyObject` objects as input arguments: + +```cpp +// addon.cc +#include +#include +#include "myobject.h" + +namespace demo { + +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; + +void CreateObject(const FunctionCallbackInfo& args) { + MyObject::NewInstance(args); +} + +void Add(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + MyObject* obj1 = node::ObjectWrap::Unwrap( + args[0]->ToObject(context).ToLocalChecked()); + MyObject* obj2 = node::ObjectWrap::Unwrap( + args[1]->ToObject(context).ToLocalChecked()); + + double sum = obj1->value() + obj2->value(); + args.GetReturnValue().Set(Number::New(isolate, sum)); +} + +void InitAll(Local exports) { + MyObject::Init(exports->GetIsolate()); + + NODE_SET_METHOD(exports, "createObject", CreateObject); + NODE_SET_METHOD(exports, "add", Add); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll) + +} // namespace demo +``` + +In `myobject.h`, a new public method is added to allow access to private values +after unwrapping the object. + +```cpp +// myobject.h +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include +#include + +namespace demo { + +class MyObject : public node::ObjectWrap { + public: + static void Init(v8::Isolate* isolate); + static void NewInstance(const v8::FunctionCallbackInfo& args); + inline double value() const { return value_; } + + private: + explicit MyObject(double value = 0); + ~MyObject(); + + static void New(const v8::FunctionCallbackInfo& args); + static v8::Global constructor; + double value_; +}; + +} // namespace demo + +#endif +``` + +The implementation of `myobject.cc` is similar to before: + +```cpp +// myobject.cc +#include +#include "myobject.h" + +namespace demo { + +using node::AddEnvironmentCleanupHook; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Global; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +// Warning! This is not thread-safe, this addon cannot be used for worker +// threads. +Global MyObject::constructor; + +MyObject::MyObject(double value) : value_(value) { +} + +MyObject::~MyObject() { +} + +void MyObject::Init(Isolate* isolate) { + // Prepare constructor template + Local tpl = FunctionTemplate::New(isolate, New); + tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + Local context = isolate->GetCurrentContext(); + constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked()); + + AddEnvironmentCleanupHook(isolate, [](void*) { + constructor.Reset(); + }, nullptr); +} + +void MyObject::New(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + if (args.IsConstructCall()) { + // Invoked as constructor: `new MyObject(...)` + double value = args[0]->IsUndefined() ? + 0 : args[0]->NumberValue(context).FromMaybe(0); + MyObject* obj = new MyObject(value); + obj->Wrap(args.This()); + args.GetReturnValue().Set(args.This()); + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + const int argc = 1; + Local argv[argc] = { args[0] }; + Local cons = Local::New(isolate, constructor); + Local instance = + cons->NewInstance(context, argc, argv).ToLocalChecked(); + args.GetReturnValue().Set(instance); + } +} + +void MyObject::NewInstance(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + + const unsigned argc = 1; + Local argv[argc] = { args[0] }; + Local cons = Local::New(isolate, constructor); + Local context = isolate->GetCurrentContext(); + Local instance = + cons->NewInstance(context, argc, argv).ToLocalChecked(); + + args.GetReturnValue().Set(instance); +} + +} // namespace demo +``` + +Test it with: + +```js +// test.js +const addon = require('./build/Release/addon'); + +const obj1 = addon.createObject(10); +const obj2 = addon.createObject(20); +const result = addon.add(obj1, obj2); + +console.log(result); +// Prints: 30 +``` + +[Electron]: https://electronjs.org/ +[Embedder's Guide]: https://v8.dev/docs/embed +[Linking to libraries included with Node.js]: #linking-to-libraries-included-with-nodejs +[Native Abstractions for Node.js]: https://github.com/nodejs/nan +[V8]: https://v8.dev/ +[`Worker`]: worker_threads.md#class-worker +[bindings]: https://github.com/TooTallNate/node-bindings +[download]: https://github.com/nodejs/node-addon-examples +[examples]: https://github.com/nodejs/nan/tree/HEAD/examples/ +[implementation considerations]: n-api.md#implications-of-abi-stability +[installation instructions]: https://github.com/nodejs/node-gyp#installation +[libuv]: https://github.com/libuv/libuv +[node-gyp]: https://github.com/nodejs/node-gyp +[require]: modules.md#requireid +[v8-docs]: https://v8docs.nodesource.com/ diff --git a/fixtures/assert.md b/fixtures/assert.md new file mode 100644 index 0000000..70f4ac6 --- /dev/null +++ b/fixtures/assert.md @@ -0,0 +1,2587 @@ +# Assert + + + +> Stability: 2 - Stable + + + +The `node:assert` module provides a set of assertion functions for verifying +invariants. + +## Strict assertion mode + + + +In strict assertion mode, non-strict methods behave like their corresponding +strict methods. For example, [`assert.deepEqual()`][] will behave like +[`assert.deepStrictEqual()`][]. + +In strict assertion mode, error messages for objects display a diff. In legacy +assertion mode, error messages for objects display the objects, often truncated. + +To use strict assertion mode: + +```mjs +import { strict as assert } from 'node:assert'; +``` + +```cjs +const assert = require('node:assert').strict; +``` + +```mjs +import assert from 'node:assert/strict'; +``` + +```cjs +const assert = require('node:assert/strict'); +``` + +Example error diff: + +```mjs +import { strict as assert } from 'node:assert'; + +assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected ... Lines skipped +// +// [ +// [ +// ... +// 2, +// + 3 +// - '3' +// ], +// ... +// 5 +// ] +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected ... Lines skipped +// +// [ +// [ +// ... +// 2, +// + 3 +// - '3' +// ], +// ... +// 5 +// ] +``` + +To deactivate the colors, use the `NO_COLOR` or `NODE_DISABLE_COLORS` +environment variables. This will also deactivate the colors in the REPL. For +more on color support in terminal environments, read the tty +[`getColorDepth()`][] documentation. + +## Legacy assertion mode + +Legacy assertion mode uses the [`==` operator][] in: + +* [`assert.deepEqual()`][] +* [`assert.equal()`][] +* [`assert.notDeepEqual()`][] +* [`assert.notEqual()`][] + +To use legacy assertion mode: + +```mjs +import assert from 'node:assert'; +``` + +```cjs +const assert = require('node:assert'); +``` + +Legacy assertion mode may have surprising results, especially when using +[`assert.deepEqual()`][]: + +```cjs +// WARNING: This does not throw an AssertionError in legacy assertion mode! +assert.deepEqual(/a/gi, new Date()); +``` + +## Class: assert.AssertionError + +* Extends: {errors.Error} + +Indicates the failure of an assertion. All errors thrown by the `node:assert` +module will be instances of the `AssertionError` class. + +### `new assert.AssertionError(options)` + + + +* `options` {Object} + * `message` {string} If provided, the error message is set to this value. + * `actual` {any} The `actual` property on the error instance. + * `expected` {any} The `expected` property on the error instance. + * `operator` {string} The `operator` property on the error instance. + * `stackStartFn` {Function} If provided, the generated stack trace omits + frames before this function. + +A subclass of `Error` that indicates the failure of an assertion. + +All instances contain the built-in `Error` properties (`message` and `name`) +and: + +* `actual` {any} Set to the `actual` argument for methods such as + [`assert.strictEqual()`][]. +* `expected` {any} Set to the `expected` value for methods such as + [`assert.strictEqual()`][]. +* `generatedMessage` {boolean} Indicates if the message was auto-generated + (`true`) or not. +* `code` {string} Value is always `ERR_ASSERTION` to show that the error is an + assertion error. +* `operator` {string} Set to the passed in operator value. + +```mjs +import assert from 'node:assert'; + +// Generate an AssertionError to compare the error message later: +const { message } = new assert.AssertionError({ + actual: 1, + expected: 2, + operator: 'strictEqual', +}); + +// Verify error output: +try { + assert.strictEqual(1, 2); +} catch (err) { + assert(err instanceof assert.AssertionError); + assert.strictEqual(err.message, message); + assert.strictEqual(err.name, 'AssertionError'); + assert.strictEqual(err.actual, 1); + assert.strictEqual(err.expected, 2); + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.operator, 'strictEqual'); + assert.strictEqual(err.generatedMessage, true); +} +``` + +```cjs +const assert = require('node:assert'); + +// Generate an AssertionError to compare the error message later: +const { message } = new assert.AssertionError({ + actual: 1, + expected: 2, + operator: 'strictEqual', +}); + +// Verify error output: +try { + assert.strictEqual(1, 2); +} catch (err) { + assert(err instanceof assert.AssertionError); + assert.strictEqual(err.message, message); + assert.strictEqual(err.name, 'AssertionError'); + assert.strictEqual(err.actual, 1); + assert.strictEqual(err.expected, 2); + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.operator, 'strictEqual'); + assert.strictEqual(err.generatedMessage, true); +} +``` + +## Class: `assert.CallTracker` + + + +> Stability: 0 - Deprecated + +This feature is deprecated and will be removed in a future version. +Please consider using alternatives such as the +[`mock`][] helper function. + +### `new assert.CallTracker()` + + + +Creates a new [`CallTracker`][] object which can be used to track if functions +were called a specific number of times. The `tracker.verify()` must be called +for the verification to take place. The usual pattern would be to call it in a +[`process.on('exit')`][] handler. + +```mjs +import assert from 'node:assert'; +import process from 'node:process'; + +const tracker = new assert.CallTracker(); + +function func() {} + +// callsfunc() must be called exactly 1 time before tracker.verify(). +const callsfunc = tracker.calls(func, 1); + +callsfunc(); + +// Calls tracker.verify() and verifies if all tracker.calls() functions have +// been called exact times. +process.on('exit', () => { + tracker.verify(); +}); +``` + +```cjs +const assert = require('node:assert'); +const process = require('node:process'); + +const tracker = new assert.CallTracker(); + +function func() {} + +// callsfunc() must be called exactly 1 time before tracker.verify(). +const callsfunc = tracker.calls(func, 1); + +callsfunc(); + +// Calls tracker.verify() and verifies if all tracker.calls() functions have +// been called exact times. +process.on('exit', () => { + tracker.verify(); +}); +``` + +### `tracker.calls([fn][, exact])` + + + +* `fn` {Function} **Default:** A no-op function. +* `exact` {number} **Default:** `1`. +* Returns: {Function} A function that wraps `fn`. + +The wrapper function is expected to be called exactly `exact` times. If the +function has not been called exactly `exact` times when +[`tracker.verify()`][] is called, then [`tracker.verify()`][] will throw an +error. + +```mjs +import assert from 'node:assert'; + +// Creates call tracker. +const tracker = new assert.CallTracker(); + +function func() {} + +// Returns a function that wraps func() that must be called exact times +// before tracker.verify(). +const callsfunc = tracker.calls(func); +``` + +```cjs +const assert = require('node:assert'); + +// Creates call tracker. +const tracker = new assert.CallTracker(); + +function func() {} + +// Returns a function that wraps func() that must be called exact times +// before tracker.verify(). +const callsfunc = tracker.calls(func); +``` + +### `tracker.getCalls(fn)` + + + +* `fn` {Function} + +* Returns: {Array} An array with all the calls to a tracked function. + +* Object {Object} + * `thisArg` {Object} + * `arguments` {Array} the arguments passed to the tracked function + +```mjs +import assert from 'node:assert'; + +const tracker = new assert.CallTracker(); + +function func() {} +const callsfunc = tracker.calls(func); +callsfunc(1, 2, 3); + +assert.deepStrictEqual(tracker.getCalls(callsfunc), + [{ thisArg: undefined, arguments: [1, 2, 3] }]); +``` + +```cjs +const assert = require('node:assert'); + +// Creates call tracker. +const tracker = new assert.CallTracker(); + +function func() {} +const callsfunc = tracker.calls(func); +callsfunc(1, 2, 3); + +assert.deepStrictEqual(tracker.getCalls(callsfunc), + [{ thisArg: undefined, arguments: [1, 2, 3] }]); +``` + +### `tracker.report()` + + + +* Returns: {Array} An array of objects containing information about the wrapper + functions returned by [`tracker.calls()`][]. +* Object {Object} + * `message` {string} + * `actual` {number} The actual number of times the function was called. + * `expected` {number} The number of times the function was expected to be + called. + * `operator` {string} The name of the function that is wrapped. + * `stack` {Object} A stack trace of the function. + +The arrays contains information about the expected and actual number of calls of +the functions that have not been called the expected number of times. + +```mjs +import assert from 'node:assert'; + +// Creates call tracker. +const tracker = new assert.CallTracker(); + +function func() {} + +// Returns a function that wraps func() that must be called exact times +// before tracker.verify(). +const callsfunc = tracker.calls(func, 2); + +// Returns an array containing information on callsfunc() +console.log(tracker.report()); +// [ +// { +// message: 'Expected the func function to be executed 2 time(s) but was +// executed 0 time(s).', +// actual: 0, +// expected: 2, +// operator: 'func', +// stack: stack trace +// } +// ] +``` + +```cjs +const assert = require('node:assert'); + +// Creates call tracker. +const tracker = new assert.CallTracker(); + +function func() {} + +// Returns a function that wraps func() that must be called exact times +// before tracker.verify(). +const callsfunc = tracker.calls(func, 2); + +// Returns an array containing information on callsfunc() +console.log(tracker.report()); +// [ +// { +// message: 'Expected the func function to be executed 2 time(s) but was +// executed 0 time(s).', +// actual: 0, +// expected: 2, +// operator: 'func', +// stack: stack trace +// } +// ] +``` + +### `tracker.reset([fn])` + + + +* `fn` {Function} a tracked function to reset. + +Reset calls of the call tracker. +If a tracked function is passed as an argument, the calls will be reset for it. +If no arguments are passed, all tracked functions will be reset. + +```mjs +import assert from 'node:assert'; + +const tracker = new assert.CallTracker(); + +function func() {} +const callsfunc = tracker.calls(func); + +callsfunc(); +// Tracker was called once +assert.strictEqual(tracker.getCalls(callsfunc).length, 1); + +tracker.reset(callsfunc); +assert.strictEqual(tracker.getCalls(callsfunc).length, 0); +``` + +```cjs +const assert = require('node:assert'); + +const tracker = new assert.CallTracker(); + +function func() {} +const callsfunc = tracker.calls(func); + +callsfunc(); +// Tracker was called once +assert.strictEqual(tracker.getCalls(callsfunc).length, 1); + +tracker.reset(callsfunc); +assert.strictEqual(tracker.getCalls(callsfunc).length, 0); +``` + +### `tracker.verify()` + + + +Iterates through the list of functions passed to +[`tracker.calls()`][] and will throw an error for functions that +have not been called the expected number of times. + +```mjs +import assert from 'node:assert'; + +// Creates call tracker. +const tracker = new assert.CallTracker(); + +function func() {} + +// Returns a function that wraps func() that must be called exact times +// before tracker.verify(). +const callsfunc = tracker.calls(func, 2); + +callsfunc(); + +// Will throw an error since callsfunc() was only called once. +tracker.verify(); +``` + +```cjs +const assert = require('node:assert'); + +// Creates call tracker. +const tracker = new assert.CallTracker(); + +function func() {} + +// Returns a function that wraps func() that must be called exact times +// before tracker.verify(). +const callsfunc = tracker.calls(func, 2); + +callsfunc(); + +// Will throw an error since callsfunc() was only called once. +tracker.verify(); +``` + +## `assert(value[, message])` + + + +* `value` {any} The input that is checked for being truthy. +* `message` {string|Error} + +An alias of [`assert.ok()`][]. + +## `assert.deepEqual(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +**Strict assertion mode** + +An alias of [`assert.deepStrictEqual()`][]. + +**Legacy assertion mode** + +> Stability: 3 - Legacy: Use [`assert.deepStrictEqual()`][] instead. + +Tests for deep equality between the `actual` and `expected` parameters. Consider +using [`assert.deepStrictEqual()`][] instead. [`assert.deepEqual()`][] can have +surprising results. + +_Deep equality_ means that the enumerable "own" properties of child objects +are also recursively evaluated by the following rules. + +### Comparison details + +* Primitive values are compared with the [`==` operator][], + with the exception of `NaN`. It is treated as being identical in case + both sides are `NaN`. +* [Type tags][Object.prototype.toString()] of objects should be the same. +* Only [enumerable "own" properties][] are considered. +* [`Error`][] names, messages, causes, and errors are always compared, + even if these are not enumerable properties. +* [Object wrappers][] are compared both as objects and unwrapped values. +* `Object` properties are compared unordered. +* [`Map`][] keys and [`Set`][] items are compared unordered. +* Recursion stops when both sides differ or both sides encounter a circular + reference. +* Implementation does not test the [`[[Prototype]]`][prototype-spec] of + objects. +* [`Symbol`][] properties are not compared. +* [`WeakMap`][] and [`WeakSet`][] comparison does not rely on their values + but only on their instances. +* [`RegExp`][] lastIndex, flags, and source are always compared, even if these + are not enumerable properties. + +The following example does not throw an [`AssertionError`][] because the +primitives are compared using the [`==` operator][]. + +```mjs +import assert from 'node:assert'; +// WARNING: This does not throw an AssertionError! + +assert.deepEqual('+00000000', false); +``` + +```cjs +const assert = require('node:assert'); +// WARNING: This does not throw an AssertionError! + +assert.deepEqual('+00000000', false); +``` + +"Deep" equality means that the enumerable "own" properties of child objects +are evaluated also: + +```mjs +import assert from 'node:assert'; + +const obj1 = { + a: { + b: 1, + }, +}; +const obj2 = { + a: { + b: 2, + }, +}; +const obj3 = { + a: { + b: 1, + }, +}; +const obj4 = { __proto__: obj1 }; + +assert.deepEqual(obj1, obj1); +// OK + +// Values of b are different: +assert.deepEqual(obj1, obj2); +// AssertionError: { a: { b: 1 } } deepEqual { a: { b: 2 } } + +assert.deepEqual(obj1, obj3); +// OK + +// Prototypes are ignored: +assert.deepEqual(obj1, obj4); +// AssertionError: { a: { b: 1 } } deepEqual {} +``` + +```cjs +const assert = require('node:assert'); + +const obj1 = { + a: { + b: 1, + }, +}; +const obj2 = { + a: { + b: 2, + }, +}; +const obj3 = { + a: { + b: 1, + }, +}; +const obj4 = { __proto__: obj1 }; + +assert.deepEqual(obj1, obj1); +// OK + +// Values of b are different: +assert.deepEqual(obj1, obj2); +// AssertionError: { a: { b: 1 } } deepEqual { a: { b: 2 } } + +assert.deepEqual(obj1, obj3); +// OK + +// Prototypes are ignored: +assert.deepEqual(obj1, obj4); +// AssertionError: { a: { b: 1 } } deepEqual {} +``` + +If the values are not equal, an [`AssertionError`][] is thrown with a `message` +property set equal to the value of the `message` parameter. If the `message` +parameter is undefined, a default error message is assigned. If the `message` +parameter is an instance of an [`Error`][] then it will be thrown instead of the +[`AssertionError`][]. + +## `assert.deepStrictEqual(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +Tests for deep equality between the `actual` and `expected` parameters. +"Deep" equality means that the enumerable "own" properties of child objects +are recursively evaluated also by the following rules. + +### Comparison details + +* Primitive values are compared using [`Object.is()`][]. +* [Type tags][Object.prototype.toString()] of objects should be the same. +* [`[[Prototype]]`][prototype-spec] of objects are compared using + the [`===` operator][]. +* Only [enumerable "own" properties][] are considered. +* [`Error`][] names, messages, causes, and errors are always compared, + even if these are not enumerable properties. + `errors` is also compared. +* Enumerable own [`Symbol`][] properties are compared as well. +* [Object wrappers][] are compared both as objects and unwrapped values. +* `Object` properties are compared unordered. +* [`Map`][] keys and [`Set`][] items are compared unordered. +* Recursion stops when both sides differ or both sides encounter a circular + reference. +* [`WeakMap`][] and [`WeakSet`][] comparison does not rely on their values. See + below for further details. +* [`RegExp`][] lastIndex, flags, and source are always compared, even if these + are not enumerable properties. + +```mjs +import assert from 'node:assert/strict'; + +// This fails because 1 !== '1'. +assert.deepStrictEqual({ a: 1 }, { a: '1' }); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// { +// + a: 1 +// - a: '1' +// } + +// The following objects don't have own properties +const date = new Date(); +const object = {}; +const fakeDate = {}; +Object.setPrototypeOf(fakeDate, Date.prototype); + +// Different [[Prototype]]: +assert.deepStrictEqual(object, fakeDate); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + {} +// - Date {} + +// Different type tags: +assert.deepStrictEqual(date, fakeDate); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + 2018-04-26T00:49:08.604Z +// - Date {} + +assert.deepStrictEqual(NaN, NaN); +// OK because Object.is(NaN, NaN) is true. + +// Different unwrapped numbers: +assert.deepStrictEqual(new Number(1), new Number(2)); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + [Number: 1] +// - [Number: 2] + +assert.deepStrictEqual(new String('foo'), Object('foo')); +// OK because the object and the string are identical when unwrapped. + +assert.deepStrictEqual(-0, -0); +// OK + +// Different zeros: +assert.deepStrictEqual(0, -0); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + 0 +// - -0 + +const symbol1 = Symbol(); +const symbol2 = Symbol(); +assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol1]: 1 }); +// OK, because it is the same symbol on both objects. + +assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol2]: 1 }); +// AssertionError [ERR_ASSERTION]: Inputs identical but not reference equal: +// +// { +// [Symbol()]: 1 +// } + +const weakMap1 = new WeakMap(); +const weakMap2 = new WeakMap([[{}, {}]]); +const weakMap3 = new WeakMap(); +weakMap3.unequal = true; + +assert.deepStrictEqual(weakMap1, weakMap2); +// OK, because it is impossible to compare the entries + +// Fails because weakMap3 has a property that weakMap1 does not contain: +assert.deepStrictEqual(weakMap1, weakMap3); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// WeakMap { +// + [items unknown] +// - [items unknown], +// - unequal: true +// } +``` + +```cjs +const assert = require('node:assert/strict'); + +// This fails because 1 !== '1'. +assert.deepStrictEqual({ a: 1 }, { a: '1' }); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// { +// + a: 1 +// - a: '1' +// } + +// The following objects don't have own properties +const date = new Date(); +const object = {}; +const fakeDate = {}; +Object.setPrototypeOf(fakeDate, Date.prototype); + +// Different [[Prototype]]: +assert.deepStrictEqual(object, fakeDate); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + {} +// - Date {} + +// Different type tags: +assert.deepStrictEqual(date, fakeDate); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + 2018-04-26T00:49:08.604Z +// - Date {} + +assert.deepStrictEqual(NaN, NaN); +// OK because Object.is(NaN, NaN) is true. + +// Different unwrapped numbers: +assert.deepStrictEqual(new Number(1), new Number(2)); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + [Number: 1] +// - [Number: 2] + +assert.deepStrictEqual(new String('foo'), Object('foo')); +// OK because the object and the string are identical when unwrapped. + +assert.deepStrictEqual(-0, -0); +// OK + +// Different zeros: +assert.deepStrictEqual(0, -0); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// + 0 +// - -0 + +const symbol1 = Symbol(); +const symbol2 = Symbol(); +assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol1]: 1 }); +// OK, because it is the same symbol on both objects. + +assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol2]: 1 }); +// AssertionError [ERR_ASSERTION]: Inputs identical but not reference equal: +// +// { +// [Symbol()]: 1 +// } + +const weakMap1 = new WeakMap(); +const weakMap2 = new WeakMap([[{}, {}]]); +const weakMap3 = new WeakMap(); +weakMap3.unequal = true; + +assert.deepStrictEqual(weakMap1, weakMap2); +// OK, because it is impossible to compare the entries + +// Fails because weakMap3 has a property that weakMap1 does not contain: +assert.deepStrictEqual(weakMap1, weakMap3); +// AssertionError: Expected inputs to be strictly deep-equal: +// + actual - expected +// +// WeakMap { +// + [items unknown] +// - [items unknown], +// - unequal: true +// } +``` + +If the values are not equal, an [`AssertionError`][] is thrown with a `message` +property set equal to the value of the `message` parameter. If the `message` +parameter is undefined, a default error message is assigned. If the `message` +parameter is an instance of an [`Error`][] then it will be thrown instead of the +`AssertionError`. + +## `assert.doesNotMatch(string, regexp[, message])` + + + +* `string` {string} +* `regexp` {RegExp} +* `message` {string|Error} + +Expects the `string` input not to match the regular expression. + +```mjs +import assert from 'node:assert/strict'; + +assert.doesNotMatch('I will fail', /fail/); +// AssertionError [ERR_ASSERTION]: The input was expected to not match the ... + +assert.doesNotMatch(123, /pass/); +// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. + +assert.doesNotMatch('I will pass', /different/); +// OK +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.doesNotMatch('I will fail', /fail/); +// AssertionError [ERR_ASSERTION]: The input was expected to not match the ... + +assert.doesNotMatch(123, /pass/); +// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. + +assert.doesNotMatch('I will pass', /different/); +// OK +``` + +If the values do match, or if the `string` argument is of another type than +`string`, an [`AssertionError`][] is thrown with a `message` property set equal +to the value of the `message` parameter. If the `message` parameter is +undefined, a default error message is assigned. If the `message` parameter is an +instance of an [`Error`][] then it will be thrown instead of the +[`AssertionError`][]. + +## `assert.doesNotReject(asyncFn[, error][, message])` + + + +* `asyncFn` {Function|Promise} +* `error` {RegExp|Function} +* `message` {string} + +Awaits the `asyncFn` promise or, if `asyncFn` is a function, immediately +calls the function and awaits the returned promise to complete. It will then +check that the promise is not rejected. + +If `asyncFn` is a function and it throws an error synchronously, +`assert.doesNotReject()` will return a rejected `Promise` with that error. If +the function does not return a promise, `assert.doesNotReject()` will return a +rejected `Promise` with an [`ERR_INVALID_RETURN_VALUE`][] error. In both cases +the error handler is skipped. + +Using `assert.doesNotReject()` is actually not useful because there is little +benefit in catching a rejection and then rejecting it again. Instead, consider +adding a comment next to the specific code path that should not reject and keep +error messages as expressive as possible. + +If specified, `error` can be a [`Class`][], [`RegExp`][], or a validation +function. See [`assert.throws()`][] for more details. + +Besides the async nature to await the completion behaves identically to +[`assert.doesNotThrow()`][]. + +```mjs +import assert from 'node:assert/strict'; + +await assert.doesNotReject( + async () => { + throw new TypeError('Wrong value'); + }, + SyntaxError, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +(async () => { + await assert.doesNotReject( + async () => { + throw new TypeError('Wrong value'); + }, + SyntaxError, + ); +})(); +``` + +```mjs +import assert from 'node:assert/strict'; + +assert.doesNotReject(Promise.reject(new TypeError('Wrong value'))) + .then(() => { + // ... + }); +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.doesNotReject(Promise.reject(new TypeError('Wrong value'))) + .then(() => { + // ... + }); +``` + +## `assert.doesNotThrow(fn[, error][, message])` + + + +* `fn` {Function} +* `error` {RegExp|Function} +* `message` {string} + +Asserts that the function `fn` does not throw an error. + +Using `assert.doesNotThrow()` is actually not useful because there +is no benefit in catching an error and then rethrowing it. Instead, consider +adding a comment next to the specific code path that should not throw and keep +error messages as expressive as possible. + +When `assert.doesNotThrow()` is called, it will immediately call the `fn` +function. + +If an error is thrown and it is the same type as that specified by the `error` +parameter, then an [`AssertionError`][] is thrown. If the error is of a +different type, or if the `error` parameter is undefined, the error is +propagated back to the caller. + +If specified, `error` can be a [`Class`][], [`RegExp`][], or a validation +function. See [`assert.throws()`][] for more details. + +The following, for instance, will throw the [`TypeError`][] because there is no +matching error type in the assertion: + +```mjs +import assert from 'node:assert/strict'; + +assert.doesNotThrow( + () => { + throw new TypeError('Wrong value'); + }, + SyntaxError, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.doesNotThrow( + () => { + throw new TypeError('Wrong value'); + }, + SyntaxError, +); +``` + +However, the following will result in an [`AssertionError`][] with the message +'Got unwanted exception...': + +```mjs +import assert from 'node:assert/strict'; + +assert.doesNotThrow( + () => { + throw new TypeError('Wrong value'); + }, + TypeError, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.doesNotThrow( + () => { + throw new TypeError('Wrong value'); + }, + TypeError, +); +``` + +If an [`AssertionError`][] is thrown and a value is provided for the `message` +parameter, the value of `message` will be appended to the [`AssertionError`][] +message: + +```mjs +import assert from 'node:assert/strict'; + +assert.doesNotThrow( + () => { + throw new TypeError('Wrong value'); + }, + /Wrong value/, + 'Whoops', +); +// Throws: AssertionError: Got unwanted exception: Whoops +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.doesNotThrow( + () => { + throw new TypeError('Wrong value'); + }, + /Wrong value/, + 'Whoops', +); +// Throws: AssertionError: Got unwanted exception: Whoops +``` + +## `assert.equal(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +**Strict assertion mode** + +An alias of [`assert.strictEqual()`][]. + +**Legacy assertion mode** + +> Stability: 3 - Legacy: Use [`assert.strictEqual()`][] instead. + +Tests shallow, coercive equality between the `actual` and `expected` parameters +using the [`==` operator][]. `NaN` is specially handled +and treated as being identical if both sides are `NaN`. + +```mjs +import assert from 'node:assert'; + +assert.equal(1, 1); +// OK, 1 == 1 +assert.equal(1, '1'); +// OK, 1 == '1' +assert.equal(NaN, NaN); +// OK + +assert.equal(1, 2); +// AssertionError: 1 == 2 +assert.equal({ a: { b: 1 } }, { a: { b: 1 } }); +// AssertionError: { a: { b: 1 } } == { a: { b: 1 } } +``` + +```cjs +const assert = require('node:assert'); + +assert.equal(1, 1); +// OK, 1 == 1 +assert.equal(1, '1'); +// OK, 1 == '1' +assert.equal(NaN, NaN); +// OK + +assert.equal(1, 2); +// AssertionError: 1 == 2 +assert.equal({ a: { b: 1 } }, { a: { b: 1 } }); +// AssertionError: { a: { b: 1 } } == { a: { b: 1 } } +``` + +If the values are not equal, an [`AssertionError`][] is thrown with a `message` +property set equal to the value of the `message` parameter. If the `message` +parameter is undefined, a default error message is assigned. If the `message` +parameter is an instance of an [`Error`][] then it will be thrown instead of the +`AssertionError`. + +## `assert.fail([message])` + + + +* `message` {string|Error} **Default:** `'Failed'` + +Throws an [`AssertionError`][] with the provided error message or a default +error message. If the `message` parameter is an instance of an [`Error`][] then +it will be thrown instead of the [`AssertionError`][]. + +```mjs +import assert from 'node:assert/strict'; + +assert.fail(); +// AssertionError [ERR_ASSERTION]: Failed + +assert.fail('boom'); +// AssertionError [ERR_ASSERTION]: boom + +assert.fail(new TypeError('need array')); +// TypeError: need array +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.fail(); +// AssertionError [ERR_ASSERTION]: Failed + +assert.fail('boom'); +// AssertionError [ERR_ASSERTION]: boom + +assert.fail(new TypeError('need array')); +// TypeError: need array +``` + +Using `assert.fail()` with more than two arguments is possible but deprecated. +See below for further details. + +## `assert.fail(actual, expected[, message[, operator[, stackStartFn]]])` + + + +> Stability: 0 - Deprecated: Use `assert.fail([message])` or other assert +> functions instead. + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} +* `operator` {string} **Default:** `'!='` +* `stackStartFn` {Function} **Default:** `assert.fail` + +If `message` is falsy, the error message is set as the values of `actual` and +`expected` separated by the provided `operator`. If just the two `actual` and +`expected` arguments are provided, `operator` will default to `'!='`. If +`message` is provided as third argument it will be used as the error message and +the other arguments will be stored as properties on the thrown object. If +`stackStartFn` is provided, all stack frames above that function will be +removed from stacktrace (see [`Error.captureStackTrace`][]). If no arguments are +given, the default message `Failed` will be used. + +```mjs +import assert from 'node:assert/strict'; + +assert.fail('a', 'b'); +// AssertionError [ERR_ASSERTION]: 'a' != 'b' + +assert.fail(1, 2, undefined, '>'); +// AssertionError [ERR_ASSERTION]: 1 > 2 + +assert.fail(1, 2, 'fail'); +// AssertionError [ERR_ASSERTION]: fail + +assert.fail(1, 2, 'whoops', '>'); +// AssertionError [ERR_ASSERTION]: whoops + +assert.fail(1, 2, new TypeError('need array')); +// TypeError: need array +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.fail('a', 'b'); +// AssertionError [ERR_ASSERTION]: 'a' != 'b' + +assert.fail(1, 2, undefined, '>'); +// AssertionError [ERR_ASSERTION]: 1 > 2 + +assert.fail(1, 2, 'fail'); +// AssertionError [ERR_ASSERTION]: fail + +assert.fail(1, 2, 'whoops', '>'); +// AssertionError [ERR_ASSERTION]: whoops + +assert.fail(1, 2, new TypeError('need array')); +// TypeError: need array +``` + +In the last three cases `actual`, `expected`, and `operator` have no +influence on the error message. + +Example use of `stackStartFn` for truncating the exception's stacktrace: + +```mjs +import assert from 'node:assert/strict'; + +function suppressFrame() { + assert.fail('a', 'b', undefined, '!==', suppressFrame); +} +suppressFrame(); +// AssertionError [ERR_ASSERTION]: 'a' !== 'b' +// at repl:1:1 +// at ContextifyScript.Script.runInThisContext (vm.js:44:33) +// ... +``` + +```cjs +const assert = require('node:assert/strict'); + +function suppressFrame() { + assert.fail('a', 'b', undefined, '!==', suppressFrame); +} +suppressFrame(); +// AssertionError [ERR_ASSERTION]: 'a' !== 'b' +// at repl:1:1 +// at ContextifyScript.Script.runInThisContext (vm.js:44:33) +// ... +``` + +## `assert.ifError(value)` + + + +* `value` {any} + +Throws `value` if `value` is not `undefined` or `null`. This is useful when +testing the `error` argument in callbacks. The stack trace contains all frames +from the error passed to `ifError()` including the potential new frames for +`ifError()` itself. + +```mjs +import assert from 'node:assert/strict'; + +assert.ifError(null); +// OK +assert.ifError(0); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 0 +assert.ifError('error'); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'error' +assert.ifError(new Error()); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Error + +// Create some random error frames. +let err; +(function errorFrame() { + err = new Error('test error'); +})(); + +(function ifErrorFrame() { + assert.ifError(err); +})(); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error +// at ifErrorFrame +// at errorFrame +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.ifError(null); +// OK +assert.ifError(0); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 0 +assert.ifError('error'); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'error' +assert.ifError(new Error()); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Error + +// Create some random error frames. +let err; +(function errorFrame() { + err = new Error('test error'); +})(); + +(function ifErrorFrame() { + assert.ifError(err); +})(); +// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error +// at ifErrorFrame +// at errorFrame +``` + +## `assert.match(string, regexp[, message])` + + + +* `string` {string} +* `regexp` {RegExp} +* `message` {string|Error} + +Expects the `string` input to match the regular expression. + +```mjs +import assert from 'node:assert/strict'; + +assert.match('I will fail', /pass/); +// AssertionError [ERR_ASSERTION]: The input did not match the regular ... + +assert.match(123, /pass/); +// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. + +assert.match('I will pass', /pass/); +// OK +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.match('I will fail', /pass/); +// AssertionError [ERR_ASSERTION]: The input did not match the regular ... + +assert.match(123, /pass/); +// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. + +assert.match('I will pass', /pass/); +// OK +``` + +If the values do not match, or if the `string` argument is of another type than +`string`, an [`AssertionError`][] is thrown with a `message` property set equal +to the value of the `message` parameter. If the `message` parameter is +undefined, a default error message is assigned. If the `message` parameter is an +instance of an [`Error`][] then it will be thrown instead of the +[`AssertionError`][]. + +## `assert.notDeepEqual(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +**Strict assertion mode** + +An alias of [`assert.notDeepStrictEqual()`][]. + +**Legacy assertion mode** + +> Stability: 3 - Legacy: Use [`assert.notDeepStrictEqual()`][] instead. + +Tests for any deep inequality. Opposite of [`assert.deepEqual()`][]. + +```mjs +import assert from 'node:assert'; + +const obj1 = { + a: { + b: 1, + }, +}; +const obj2 = { + a: { + b: 2, + }, +}; +const obj3 = { + a: { + b: 1, + }, +}; +const obj4 = { __proto__: obj1 }; + +assert.notDeepEqual(obj1, obj1); +// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } + +assert.notDeepEqual(obj1, obj2); +// OK + +assert.notDeepEqual(obj1, obj3); +// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } + +assert.notDeepEqual(obj1, obj4); +// OK +``` + +```cjs +const assert = require('node:assert'); + +const obj1 = { + a: { + b: 1, + }, +}; +const obj2 = { + a: { + b: 2, + }, +}; +const obj3 = { + a: { + b: 1, + }, +}; +const obj4 = { __proto__: obj1 }; + +assert.notDeepEqual(obj1, obj1); +// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } + +assert.notDeepEqual(obj1, obj2); +// OK + +assert.notDeepEqual(obj1, obj3); +// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } + +assert.notDeepEqual(obj1, obj4); +// OK +``` + +If the values are deeply equal, an [`AssertionError`][] is thrown with a +`message` property set equal to the value of the `message` parameter. If the +`message` parameter is undefined, a default error message is assigned. If the +`message` parameter is an instance of an [`Error`][] then it will be thrown +instead of the `AssertionError`. + +## `assert.notDeepStrictEqual(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +Tests for deep strict inequality. Opposite of [`assert.deepStrictEqual()`][]. + +```mjs +import assert from 'node:assert/strict'; + +assert.notDeepStrictEqual({ a: 1 }, { a: '1' }); +// OK +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.notDeepStrictEqual({ a: 1 }, { a: '1' }); +// OK +``` + +If the values are deeply and strictly equal, an [`AssertionError`][] is thrown +with a `message` property set equal to the value of the `message` parameter. If +the `message` parameter is undefined, a default error message is assigned. If +the `message` parameter is an instance of an [`Error`][] then it will be thrown +instead of the [`AssertionError`][]. + +## `assert.notEqual(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +**Strict assertion mode** + +An alias of [`assert.notStrictEqual()`][]. + +**Legacy assertion mode** + +> Stability: 3 - Legacy: Use [`assert.notStrictEqual()`][] instead. + +Tests shallow, coercive inequality with the [`!=` operator][]. `NaN` is +specially handled and treated as being identical if both sides are `NaN`. + +```mjs +import assert from 'node:assert'; + +assert.notEqual(1, 2); +// OK + +assert.notEqual(1, 1); +// AssertionError: 1 != 1 + +assert.notEqual(1, '1'); +// AssertionError: 1 != '1' +``` + +```cjs +const assert = require('node:assert'); + +assert.notEqual(1, 2); +// OK + +assert.notEqual(1, 1); +// AssertionError: 1 != 1 + +assert.notEqual(1, '1'); +// AssertionError: 1 != '1' +``` + +If the values are equal, an [`AssertionError`][] is thrown with a `message` +property set equal to the value of the `message` parameter. If the `message` +parameter is undefined, a default error message is assigned. If the `message` +parameter is an instance of an [`Error`][] then it will be thrown instead of the +`AssertionError`. + +## `assert.notStrictEqual(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +Tests strict inequality between the `actual` and `expected` parameters as +determined by [`Object.is()`][]. + +```mjs +import assert from 'node:assert/strict'; + +assert.notStrictEqual(1, 2); +// OK + +assert.notStrictEqual(1, 1); +// AssertionError [ERR_ASSERTION]: Expected "actual" to be strictly unequal to: +// +// 1 + +assert.notStrictEqual(1, '1'); +// OK +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.notStrictEqual(1, 2); +// OK + +assert.notStrictEqual(1, 1); +// AssertionError [ERR_ASSERTION]: Expected "actual" to be strictly unequal to: +// +// 1 + +assert.notStrictEqual(1, '1'); +// OK +``` + +If the values are strictly equal, an [`AssertionError`][] is thrown with a +`message` property set equal to the value of the `message` parameter. If the +`message` parameter is undefined, a default error message is assigned. If the +`message` parameter is an instance of an [`Error`][] then it will be thrown +instead of the `AssertionError`. + +## `assert.ok(value[, message])` + + + +* `value` {any} +* `message` {string|Error} + +Tests if `value` is truthy. It is equivalent to +`assert.equal(!!value, true, message)`. + +If `value` is not truthy, an [`AssertionError`][] is thrown with a `message` +property set equal to the value of the `message` parameter. If the `message` +parameter is `undefined`, a default error message is assigned. If the `message` +parameter is an instance of an [`Error`][] then it will be thrown instead of the +`AssertionError`. +If no arguments are passed in at all `message` will be set to the string: +``'No value argument passed to `assert.ok()`'``. + +Be aware that in the `repl` the error message will be different to the one +thrown in a file! See below for further details. + +```mjs +import assert from 'node:assert/strict'; + +assert.ok(true); +// OK +assert.ok(1); +// OK + +assert.ok(); +// AssertionError: No value argument passed to `assert.ok()` + +assert.ok(false, 'it\'s false'); +// AssertionError: it's false + +// In the repl: +assert.ok(typeof 123 === 'string'); +// AssertionError: false == true + +// In a file (e.g. test.js): +assert.ok(typeof 123 === 'string'); +// AssertionError: The expression evaluated to a falsy value: +// +// assert.ok(typeof 123 === 'string') + +assert.ok(false); +// AssertionError: The expression evaluated to a falsy value: +// +// assert.ok(false) + +assert.ok(0); +// AssertionError: The expression evaluated to a falsy value: +// +// assert.ok(0) +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.ok(true); +// OK +assert.ok(1); +// OK + +assert.ok(); +// AssertionError: No value argument passed to `assert.ok()` + +assert.ok(false, 'it\'s false'); +// AssertionError: it's false + +// In the repl: +assert.ok(typeof 123 === 'string'); +// AssertionError: false == true + +// In a file (e.g. test.js): +assert.ok(typeof 123 === 'string'); +// AssertionError: The expression evaluated to a falsy value: +// +// assert.ok(typeof 123 === 'string') + +assert.ok(false); +// AssertionError: The expression evaluated to a falsy value: +// +// assert.ok(false) + +assert.ok(0); +// AssertionError: The expression evaluated to a falsy value: +// +// assert.ok(0) +``` + +```mjs +import assert from 'node:assert/strict'; + +// Using `assert()` works the same: +assert(0); +// AssertionError: The expression evaluated to a falsy value: +// +// assert(0) +``` + +```cjs +const assert = require('node:assert'); + +// Using `assert()` works the same: +assert(0); +// AssertionError: The expression evaluated to a falsy value: +// +// assert(0) +``` + +## `assert.rejects(asyncFn[, error][, message])` + + + +* `asyncFn` {Function|Promise} +* `error` {RegExp|Function|Object|Error} +* `message` {string} + +Awaits the `asyncFn` promise or, if `asyncFn` is a function, immediately +calls the function and awaits the returned promise to complete. It will then +check that the promise is rejected. + +If `asyncFn` is a function and it throws an error synchronously, +`assert.rejects()` will return a rejected `Promise` with that error. If the +function does not return a promise, `assert.rejects()` will return a rejected +`Promise` with an [`ERR_INVALID_RETURN_VALUE`][] error. In both cases the error +handler is skipped. + +Besides the async nature to await the completion behaves identically to +[`assert.throws()`][]. + +If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function, +an object where each property will be tested for, or an instance of error where +each property will be tested for including the non-enumerable `message` and +`name` properties. + +If specified, `message` will be the message provided by the [`AssertionError`][] +if the `asyncFn` fails to reject. + +```mjs +import assert from 'node:assert/strict'; + +await assert.rejects( + async () => { + throw new TypeError('Wrong value'); + }, + { + name: 'TypeError', + message: 'Wrong value', + }, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +(async () => { + await assert.rejects( + async () => { + throw new TypeError('Wrong value'); + }, + { + name: 'TypeError', + message: 'Wrong value', + }, + ); +})(); +``` + +```mjs +import assert from 'node:assert/strict'; + +await assert.rejects( + async () => { + throw new TypeError('Wrong value'); + }, + (err) => { + assert.strictEqual(err.name, 'TypeError'); + assert.strictEqual(err.message, 'Wrong value'); + return true; + }, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +(async () => { + await assert.rejects( + async () => { + throw new TypeError('Wrong value'); + }, + (err) => { + assert.strictEqual(err.name, 'TypeError'); + assert.strictEqual(err.message, 'Wrong value'); + return true; + }, + ); +})(); +``` + +```mjs +import assert from 'node:assert/strict'; + +assert.rejects( + Promise.reject(new Error('Wrong value')), + Error, +).then(() => { + // ... +}); +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.rejects( + Promise.reject(new Error('Wrong value')), + Error, +).then(() => { + // ... +}); +``` + +`error` cannot be a string. If a string is provided as the second +argument, then `error` is assumed to be omitted and the string will be used for +`message` instead. This can lead to easy-to-miss mistakes. Please read the +example in [`assert.throws()`][] carefully if using a string as the second +argument gets considered. + +## `assert.strictEqual(actual, expected[, message])` + + + +* `actual` {any} +* `expected` {any} +* `message` {string|Error} + +Tests strict equality between the `actual` and `expected` parameters as +determined by [`Object.is()`][]. + +```mjs +import assert from 'node:assert/strict'; + +assert.strictEqual(1, 2); +// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: +// +// 1 !== 2 + +assert.strictEqual(1, 1); +// OK + +assert.strictEqual('Hello foobar', 'Hello World!'); +// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: +// + actual - expected +// +// + 'Hello foobar' +// - 'Hello World!' +// ^ + +const apples = 1; +const oranges = 2; +assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`); +// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2 + +assert.strictEqual(1, '1', new TypeError('Inputs are not identical')); +// TypeError: Inputs are not identical +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.strictEqual(1, 2); +// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: +// +// 1 !== 2 + +assert.strictEqual(1, 1); +// OK + +assert.strictEqual('Hello foobar', 'Hello World!'); +// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: +// + actual - expected +// +// + 'Hello foobar' +// - 'Hello World!' +// ^ + +const apples = 1; +const oranges = 2; +assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`); +// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2 + +assert.strictEqual(1, '1', new TypeError('Inputs are not identical')); +// TypeError: Inputs are not identical +``` + +If the values are not strictly equal, an [`AssertionError`][] is thrown with a +`message` property set equal to the value of the `message` parameter. If the +`message` parameter is undefined, a default error message is assigned. If the +`message` parameter is an instance of an [`Error`][] then it will be thrown +instead of the [`AssertionError`][]. + +## `assert.throws(fn[, error][, message])` + + + +* `fn` {Function} +* `error` {RegExp|Function|Object|Error} +* `message` {string} + +Expects the function `fn` to throw an error. + +If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function, +a validation object where each property will be tested for strict deep equality, +or an instance of error where each property will be tested for strict deep +equality including the non-enumerable `message` and `name` properties. When +using an object, it is also possible to use a regular expression, when +validating against a string property. See below for examples. + +If specified, `message` will be appended to the message provided by the +`AssertionError` if the `fn` call fails to throw or in case the error validation +fails. + +Custom validation object/error instance: + +```mjs +import assert from 'node:assert/strict'; + +const err = new TypeError('Wrong value'); +err.code = 404; +err.foo = 'bar'; +err.info = { + nested: true, + baz: 'text', +}; +err.reg = /abc/i; + +assert.throws( + () => { + throw err; + }, + { + name: 'TypeError', + message: 'Wrong value', + info: { + nested: true, + baz: 'text', + }, + // Only properties on the validation object will be tested for. + // Using nested objects requires all properties to be present. Otherwise + // the validation is going to fail. + }, +); + +// Using regular expressions to validate error properties: +assert.throws( + () => { + throw err; + }, + { + // The `name` and `message` properties are strings and using regular + // expressions on those will match against the string. If they fail, an + // error is thrown. + name: /^TypeError$/, + message: /Wrong/, + foo: 'bar', + info: { + nested: true, + // It is not possible to use regular expressions for nested properties! + baz: 'text', + }, + // The `reg` property contains a regular expression and only if the + // validation object contains an identical regular expression, it is going + // to pass. + reg: /abc/i, + }, +); + +// Fails due to the different `message` and `name` properties: +assert.throws( + () => { + const otherErr = new Error('Not found'); + // Copy all enumerable properties from `err` to `otherErr`. + for (const [key, value] of Object.entries(err)) { + otherErr[key] = value; + } + throw otherErr; + }, + // The error's `message` and `name` properties will also be checked when using + // an error as validation object. + err, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +const err = new TypeError('Wrong value'); +err.code = 404; +err.foo = 'bar'; +err.info = { + nested: true, + baz: 'text', +}; +err.reg = /abc/i; + +assert.throws( + () => { + throw err; + }, + { + name: 'TypeError', + message: 'Wrong value', + info: { + nested: true, + baz: 'text', + }, + // Only properties on the validation object will be tested for. + // Using nested objects requires all properties to be present. Otherwise + // the validation is going to fail. + }, +); + +// Using regular expressions to validate error properties: +assert.throws( + () => { + throw err; + }, + { + // The `name` and `message` properties are strings and using regular + // expressions on those will match against the string. If they fail, an + // error is thrown. + name: /^TypeError$/, + message: /Wrong/, + foo: 'bar', + info: { + nested: true, + // It is not possible to use regular expressions for nested properties! + baz: 'text', + }, + // The `reg` property contains a regular expression and only if the + // validation object contains an identical regular expression, it is going + // to pass. + reg: /abc/i, + }, +); + +// Fails due to the different `message` and `name` properties: +assert.throws( + () => { + const otherErr = new Error('Not found'); + // Copy all enumerable properties from `err` to `otherErr`. + for (const [key, value] of Object.entries(err)) { + otherErr[key] = value; + } + throw otherErr; + }, + // The error's `message` and `name` properties will also be checked when using + // an error as validation object. + err, +); +``` + +Validate instanceof using constructor: + +```mjs +import assert from 'node:assert/strict'; + +assert.throws( + () => { + throw new Error('Wrong value'); + }, + Error, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.throws( + () => { + throw new Error('Wrong value'); + }, + Error, +); +``` + +Validate error message using [`RegExp`][]: + +Using a regular expression runs `.toString` on the error object, and will +therefore also include the error name. + +```mjs +import assert from 'node:assert/strict'; + +assert.throws( + () => { + throw new Error('Wrong value'); + }, + /^Error: Wrong value$/, +); +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.throws( + () => { + throw new Error('Wrong value'); + }, + /^Error: Wrong value$/, +); +``` + +Custom error validation: + +The function must return `true` to indicate all internal validations passed. +It will otherwise fail with an [`AssertionError`][]. + +```mjs +import assert from 'node:assert/strict'; + +assert.throws( + () => { + throw new Error('Wrong value'); + }, + (err) => { + assert(err instanceof Error); + assert(/value/.test(err)); + // Avoid returning anything from validation functions besides `true`. + // Otherwise, it's not clear what part of the validation failed. Instead, + // throw an error about the specific validation that failed (as done in this + // example) and add as much helpful debugging information to that error as + // possible. + return true; + }, + 'unexpected error', +); +``` + +```cjs +const assert = require('node:assert/strict'); + +assert.throws( + () => { + throw new Error('Wrong value'); + }, + (err) => { + assert(err instanceof Error); + assert(/value/.test(err)); + // Avoid returning anything from validation functions besides `true`. + // Otherwise, it's not clear what part of the validation failed. Instead, + // throw an error about the specific validation that failed (as done in this + // example) and add as much helpful debugging information to that error as + // possible. + return true; + }, + 'unexpected error', +); +``` + +`error` cannot be a string. If a string is provided as the second +argument, then `error` is assumed to be omitted and the string will be used for +`message` instead. This can lead to easy-to-miss mistakes. Using the same +message as the thrown error message is going to result in an +`ERR_AMBIGUOUS_ARGUMENT` error. Please read the example below carefully if using +a string as the second argument gets considered: + +```mjs +import assert from 'node:assert/strict'; + +function throwingFirst() { + throw new Error('First'); +} + +function throwingSecond() { + throw new Error('Second'); +} + +function notThrowing() {} + +// The second argument is a string and the input function threw an Error. +// The first case will not throw as it does not match for the error message +// thrown by the input function! +assert.throws(throwingFirst, 'Second'); +// In the next example the message has no benefit over the message from the +// error and since it is not clear if the user intended to actually match +// against the error message, Node.js throws an `ERR_AMBIGUOUS_ARGUMENT` error. +assert.throws(throwingSecond, 'Second'); +// TypeError [ERR_AMBIGUOUS_ARGUMENT] + +// The string is only used (as message) in case the function does not throw: +assert.throws(notThrowing, 'Second'); +// AssertionError [ERR_ASSERTION]: Missing expected exception: Second + +// If it was intended to match for the error message do this instead: +// It does not throw because the error messages match. +assert.throws(throwingSecond, /Second$/); + +// If the error message does not match, an AssertionError is thrown. +assert.throws(throwingFirst, /Second$/); +// AssertionError [ERR_ASSERTION] +``` + +```cjs +const assert = require('node:assert/strict'); + +function throwingFirst() { + throw new Error('First'); +} + +function throwingSecond() { + throw new Error('Second'); +} + +function notThrowing() {} + +// The second argument is a string and the input function threw an Error. +// The first case will not throw as it does not match for the error message +// thrown by the input function! +assert.throws(throwingFirst, 'Second'); +// In the next example the message has no benefit over the message from the +// error and since it is not clear if the user intended to actually match +// against the error message, Node.js throws an `ERR_AMBIGUOUS_ARGUMENT` error. +assert.throws(throwingSecond, 'Second'); +// TypeError [ERR_AMBIGUOUS_ARGUMENT] + +// The string is only used (as message) in case the function does not throw: +assert.throws(notThrowing, 'Second'); +// AssertionError [ERR_ASSERTION]: Missing expected exception: Second + +// If it was intended to match for the error message do this instead: +// It does not throw because the error messages match. +assert.throws(throwingSecond, /Second$/); + +// If the error message does not match, an AssertionError is thrown. +assert.throws(throwingFirst, /Second$/); +// AssertionError [ERR_ASSERTION] +``` + +Due to the confusing error-prone notation, avoid a string as the second +argument. + +[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript +[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring +[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality +[`===` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality +[`==` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality +[`AssertionError`]: #class-assertassertionerror +[`CallTracker`]: #class-assertcalltracker +[`Class`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes +[`ERR_INVALID_RETURN_VALUE`]: errors.md#err_invalid_return_value +[`Error.captureStackTrace`]: errors.md#errorcapturestacktracetargetobject-constructoropt +[`Error`]: errors.md#class-error +[`Map`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map +[`Object.is()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is +[`RegExp`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[`Set`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set +[`Symbol`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol +[`TypeError`]: errors.md#class-typeerror +[`WeakMap`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap +[`WeakSet`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet +[`assert.deepEqual()`]: #assertdeepequalactual-expected-message +[`assert.deepStrictEqual()`]: #assertdeepstrictequalactual-expected-message +[`assert.doesNotThrow()`]: #assertdoesnotthrowfn-error-message +[`assert.equal()`]: #assertequalactual-expected-message +[`assert.notDeepEqual()`]: #assertnotdeepequalactual-expected-message +[`assert.notDeepStrictEqual()`]: #assertnotdeepstrictequalactual-expected-message +[`assert.notEqual()`]: #assertnotequalactual-expected-message +[`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message +[`assert.ok()`]: #assertokvalue-message +[`assert.strictEqual()`]: #assertstrictequalactual-expected-message +[`assert.throws()`]: #assertthrowsfn-error-message +[`getColorDepth()`]: tty.md#writestreamgetcolordepthenv +[`mock`]: test.md#mocking +[`process.on('exit')`]: process.md#event-exit +[`tracker.calls()`]: #trackercallsfn-exact +[`tracker.verify()`]: #trackerverify +[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties +[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots diff --git a/fixtures/async_context.md b/fixtures/async_context.md new file mode 100644 index 0000000..d1be2fb --- /dev/null +++ b/fixtures/async_context.md @@ -0,0 +1,888 @@ +# Asynchronous context tracking + + + +> Stability: 2 - Stable + + + +## Introduction + +These classes are used to associate state and propagate it throughout +callbacks and promise chains. +They allow storing data throughout the lifetime of a web request +or any other asynchronous duration. It is similar to thread-local storage +in other languages. + +The `AsyncLocalStorage` and `AsyncResource` classes are part of the +`node:async_hooks` module: + +```mjs +import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'; +``` + +```cjs +const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks'); +``` + +## Class: `AsyncLocalStorage` + + + +This class creates stores that stay coherent through asynchronous operations. + +While you can create your own implementation on top of the `node:async_hooks` +module, `AsyncLocalStorage` should be preferred as it is a performant and memory +safe implementation that involves significant optimizations that are non-obvious +to implement. + +The following example uses `AsyncLocalStorage` to build a simple logger +that assigns IDs to incoming HTTP requests and includes them in messages +logged within each request. + +```mjs +import http from 'node:http'; +import { AsyncLocalStorage } from 'node:async_hooks'; + +const asyncLocalStorage = new AsyncLocalStorage(); + +function logWithId(msg) { + const id = asyncLocalStorage.getStore(); + console.log(`${id !== undefined ? id : '-'}:`, msg); +} + +let idSeq = 0; +http.createServer((req, res) => { + asyncLocalStorage.run(idSeq++, () => { + logWithId('start'); + // Imagine any chain of async operations here + setImmediate(() => { + logWithId('finish'); + res.end(); + }); + }); +}).listen(8080); + +http.get('http://localhost:8080'); +http.get('http://localhost:8080'); +// Prints: +// 0: start +// 1: start +// 0: finish +// 1: finish +``` + +```cjs +const http = require('node:http'); +const { AsyncLocalStorage } = require('node:async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +function logWithId(msg) { + const id = asyncLocalStorage.getStore(); + console.log(`${id !== undefined ? id : '-'}:`, msg); +} + +let idSeq = 0; +http.createServer((req, res) => { + asyncLocalStorage.run(idSeq++, () => { + logWithId('start'); + // Imagine any chain of async operations here + setImmediate(() => { + logWithId('finish'); + res.end(); + }); + }); +}).listen(8080); + +http.get('http://localhost:8080'); +http.get('http://localhost:8080'); +// Prints: +// 0: start +// 1: start +// 0: finish +// 1: finish +``` + +Each instance of `AsyncLocalStorage` maintains an independent storage context. +Multiple instances can safely exist simultaneously without risk of interfering +with each other's data. + +### `new AsyncLocalStorage()` + + + +Creates a new instance of `AsyncLocalStorage`. Store is only provided within a +`run()` call or after an `enterWith()` call. + +### Static method: `AsyncLocalStorage.bind(fn)` + + + +> Stability: 1 - Experimental + +* `fn` {Function} The function to bind to the current execution context. +* Returns: {Function} A new function that calls `fn` within the captured + execution context. + +Binds the given function to the current execution context. + +### Static method: `AsyncLocalStorage.snapshot()` + + + +> Stability: 1 - Experimental + +* Returns: {Function} A new function with the signature + `(fn: (...args) : R, ...args) : R`. + +Captures the current execution context and returns a function that accepts a +function as an argument. Whenever the returned function is called, it +calls the function passed to it within the captured context. + +```js +const asyncLocalStorage = new AsyncLocalStorage(); +const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot()); +const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore())); +console.log(result); // returns 123 +``` + +AsyncLocalStorage.snapshot() can replace the use of AsyncResource for simple +async context tracking purposes, for example: + +```js +class Foo { + #runInAsyncScope = AsyncLocalStorage.snapshot(); + + get() { return this.#runInAsyncScope(() => asyncLocalStorage.getStore()); } +} + +const foo = asyncLocalStorage.run(123, () => new Foo()); +console.log(asyncLocalStorage.run(321, () => foo.get())); // returns 123 +``` + +### `asyncLocalStorage.disable()` + + + +> Stability: 1 - Experimental + +Disables the instance of `AsyncLocalStorage`. All subsequent calls +to `asyncLocalStorage.getStore()` will return `undefined` until +`asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()` is called again. + +When calling `asyncLocalStorage.disable()`, all current contexts linked to the +instance will be exited. + +Calling `asyncLocalStorage.disable()` is required before the +`asyncLocalStorage` can be garbage collected. This does not apply to stores +provided by the `asyncLocalStorage`, as those objects are garbage collected +along with the corresponding async resources. + +Use this method when the `asyncLocalStorage` is not in use anymore +in the current process. + +### `asyncLocalStorage.getStore()` + + + +* Returns: {any} + +Returns the current store. +If called outside of an asynchronous context initialized by +calling `asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()`, it +returns `undefined`. + +### `asyncLocalStorage.enterWith(store)` + + + +> Stability: 1 - Experimental + +* `store` {any} + +Transitions into the context for the remainder of the current +synchronous execution and then persists the store through any following +asynchronous calls. + +Example: + +```js +const store = { id: 1 }; +// Replaces previous store with the given store object +asyncLocalStorage.enterWith(store); +asyncLocalStorage.getStore(); // Returns the store object +someAsyncOperation(() => { + asyncLocalStorage.getStore(); // Returns the same object +}); +``` + +This transition will continue for the _entire_ synchronous execution. +This means that if, for example, the context is entered within an event +handler subsequent event handlers will also run within that context unless +specifically bound to another context with an `AsyncResource`. That is why +`run()` should be preferred over `enterWith()` unless there are strong reasons +to use the latter method. + +```js +const store = { id: 1 }; + +emitter.on('my-event', () => { + asyncLocalStorage.enterWith(store); +}); +emitter.on('my-event', () => { + asyncLocalStorage.getStore(); // Returns the same object +}); + +asyncLocalStorage.getStore(); // Returns undefined +emitter.emit('my-event'); +asyncLocalStorage.getStore(); // Returns the same object +``` + +### `asyncLocalStorage.run(store, callback[, ...args])` + + + +* `store` {any} +* `callback` {Function} +* `...args` {any} + +Runs a function synchronously within a context and returns its +return value. The store is not accessible outside of the callback function. +The store is accessible to any asynchronous operations created within the +callback. + +The optional `args` are passed to the callback function. + +If the callback function throws an error, the error is thrown by `run()` too. +The stacktrace is not impacted by this call and the context is exited. + +Example: + +```js +const store = { id: 2 }; +try { + asyncLocalStorage.run(store, () => { + asyncLocalStorage.getStore(); // Returns the store object + setTimeout(() => { + asyncLocalStorage.getStore(); // Returns the store object + }, 200); + throw new Error(); + }); +} catch (e) { + asyncLocalStorage.getStore(); // Returns undefined + // The error will be caught here +} +``` + +### `asyncLocalStorage.exit(callback[, ...args])` + + + +> Stability: 1 - Experimental + +* `callback` {Function} +* `...args` {any} + +Runs a function synchronously outside of a context and returns its +return value. The store is not accessible within the callback function or +the asynchronous operations created within the callback. Any `getStore()` +call done within the callback function will always return `undefined`. + +The optional `args` are passed to the callback function. + +If the callback function throws an error, the error is thrown by `exit()` too. +The stacktrace is not impacted by this call and the context is re-entered. + +Example: + +```js +// Within a call to run +try { + asyncLocalStorage.getStore(); // Returns the store object or value + asyncLocalStorage.exit(() => { + asyncLocalStorage.getStore(); // Returns undefined + throw new Error(); + }); +} catch (e) { + asyncLocalStorage.getStore(); // Returns the same object or value + // The error will be caught here +} +``` + +### Usage with `async/await` + +If, within an async function, only one `await` call is to run within a context, +the following pattern should be used: + +```js +async function fn() { + await asyncLocalStorage.run(new Map(), () => { + asyncLocalStorage.getStore().set('key', value); + return foo(); // The return value of foo will be awaited + }); +} +``` + +In this example, the store is only available in the callback function and the +functions called by `foo`. Outside of `run`, calling `getStore` will return +`undefined`. + +### Troubleshooting: Context loss + +In most cases, `AsyncLocalStorage` works without issues. In rare situations, the +current store is lost in one of the asynchronous operations. + +If your code is callback-based, it is enough to promisify it with +[`util.promisify()`][] so it starts working with native promises. + +If you need to use a callback-based API or your code assumes +a custom thenable implementation, use the [`AsyncResource`][] class +to associate the asynchronous operation with the correct execution context. +Find the function call responsible for the context loss by logging the content +of `asyncLocalStorage.getStore()` after the calls you suspect are responsible +for the loss. When the code logs `undefined`, the last callback called is +probably responsible for the context loss. + +## Class: `AsyncResource` + + + +The class `AsyncResource` is designed to be extended by the embedder's async +resources. Using this, users can easily trigger the lifetime events of their +own resources. + +The `init` hook will trigger when an `AsyncResource` is instantiated. + +The following is an overview of the `AsyncResource` API. + +```mjs +import { AsyncResource, executionAsyncId } from 'node:async_hooks'; + +// AsyncResource() is meant to be extended. Instantiating a +// new AsyncResource() also triggers init. If triggerAsyncId is omitted then +// async_hook.executionAsyncId() is used. +const asyncResource = new AsyncResource( + type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }, +); + +// Run a function in the execution context of the resource. This will +// * establish the context of the resource +// * trigger the AsyncHooks before callbacks +// * call the provided function `fn` with the supplied arguments +// * trigger the AsyncHooks after callbacks +// * restore the original execution context +asyncResource.runInAsyncScope(fn, thisArg, ...args); + +// Call AsyncHooks destroy callbacks. +asyncResource.emitDestroy(); + +// Return the unique ID assigned to the AsyncResource instance. +asyncResource.asyncId(); + +// Return the trigger ID for the AsyncResource instance. +asyncResource.triggerAsyncId(); +``` + +```cjs +const { AsyncResource, executionAsyncId } = require('node:async_hooks'); + +// AsyncResource() is meant to be extended. Instantiating a +// new AsyncResource() also triggers init. If triggerAsyncId is omitted then +// async_hook.executionAsyncId() is used. +const asyncResource = new AsyncResource( + type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }, +); + +// Run a function in the execution context of the resource. This will +// * establish the context of the resource +// * trigger the AsyncHooks before callbacks +// * call the provided function `fn` with the supplied arguments +// * trigger the AsyncHooks after callbacks +// * restore the original execution context +asyncResource.runInAsyncScope(fn, thisArg, ...args); + +// Call AsyncHooks destroy callbacks. +asyncResource.emitDestroy(); + +// Return the unique ID assigned to the AsyncResource instance. +asyncResource.asyncId(); + +// Return the trigger ID for the AsyncResource instance. +asyncResource.triggerAsyncId(); +``` + +### `new AsyncResource(type[, options])` + +* `type` {string} The type of async event. +* `options` {Object} + * `triggerAsyncId` {number} The ID of the execution context that created this + async event. **Default:** `executionAsyncId()`. + * `requireManualDestroy` {boolean} If set to `true`, disables `emitDestroy` + when the object is garbage collected. This usually does not need to be set + (even if `emitDestroy` is called manually), unless the resource's `asyncId` + is retrieved and the sensitive API's `emitDestroy` is called with it. + When set to `false`, the `emitDestroy` call on garbage collection + will only take place if there is at least one active `destroy` hook. + **Default:** `false`. + +Example usage: + +```js +class DBQuery extends AsyncResource { + constructor(db) { + super('DBQuery'); + this.db = db; + } + + getInfo(query, callback) { + this.db.get(query, (err, data) => { + this.runInAsyncScope(callback, null, err, data); + }); + } + + close() { + this.db = null; + this.emitDestroy(); + } +} +``` + +### Static method: `AsyncResource.bind(fn[, type[, thisArg]])` + + + +* `fn` {Function} The function to bind to the current execution context. +* `type` {string} An optional name to associate with the underlying + `AsyncResource`. +* `thisArg` {any} + +Binds the given function to the current execution context. + +### `asyncResource.bind(fn[, thisArg])` + + + +* `fn` {Function} The function to bind to the current `AsyncResource`. +* `thisArg` {any} + +Binds the given function to execute to this `AsyncResource`'s scope. + +### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])` + + + +* `fn` {Function} The function to call in the execution context of this async + resource. +* `thisArg` {any} The receiver to be used for the function call. +* `...args` {any} Optional arguments to pass to the function. + +Call the provided function with the provided arguments in the execution context +of the async resource. This will establish the context, trigger the AsyncHooks +before callbacks, call the function, trigger the AsyncHooks after callbacks, and +then restore the original execution context. + +### `asyncResource.emitDestroy()` + +* Returns: {AsyncResource} A reference to `asyncResource`. + +Call all `destroy` hooks. This should only ever be called once. An error will +be thrown if it is called more than once. This **must** be manually called. If +the resource is left to be collected by the GC then the `destroy` hooks will +never be called. + +### `asyncResource.asyncId()` + +* Returns: {number} The unique `asyncId` assigned to the resource. + +### `asyncResource.triggerAsyncId()` + +* Returns: {number} The same `triggerAsyncId` that is passed to the + `AsyncResource` constructor. + + + +### Using `AsyncResource` for a `Worker` thread pool + +The following example shows how to use the `AsyncResource` class to properly +provide async tracking for a [`Worker`][] pool. Other resource pools, such as +database connection pools, can follow a similar model. + +Assuming that the task is adding two numbers, using a file named +`task_processor.js` with the following content: + +```mjs +import { parentPort } from 'node:worker_threads'; +parentPort.on('message', (task) => { + parentPort.postMessage(task.a + task.b); +}); +``` + +```cjs +const { parentPort } = require('node:worker_threads'); +parentPort.on('message', (task) => { + parentPort.postMessage(task.a + task.b); +}); +``` + +a Worker pool around it could use the following structure: + +```mjs +import { AsyncResource } from 'node:async_hooks'; +import { EventEmitter } from 'node:events'; +import path from 'node:path'; +import { Worker } from 'node:worker_threads'; + +const kTaskInfo = Symbol('kTaskInfo'); +const kWorkerFreedEvent = Symbol('kWorkerFreedEvent'); + +class WorkerPoolTaskInfo extends AsyncResource { + constructor(callback) { + super('WorkerPoolTaskInfo'); + this.callback = callback; + } + + done(err, result) { + this.runInAsyncScope(this.callback, null, err, result); + this.emitDestroy(); // `TaskInfo`s are used only once. + } +} + +export default class WorkerPool extends EventEmitter { + constructor(numThreads) { + super(); + this.numThreads = numThreads; + this.workers = []; + this.freeWorkers = []; + this.tasks = []; + + for (let i = 0; i < numThreads; i++) + this.addNewWorker(); + + // Any time the kWorkerFreedEvent is emitted, dispatch + // the next task pending in the queue, if any. + this.on(kWorkerFreedEvent, () => { + if (this.tasks.length > 0) { + const { task, callback } = this.tasks.shift(); + this.runTask(task, callback); + } + }); + } + + addNewWorker() { + const worker = new Worker(new URL('task_processor.js', import.meta.url)); + worker.on('message', (result) => { + // In case of success: Call the callback that was passed to `runTask`, + // remove the `TaskInfo` associated with the Worker, and mark it as free + // again. + worker[kTaskInfo].done(null, result); + worker[kTaskInfo] = null; + this.freeWorkers.push(worker); + this.emit(kWorkerFreedEvent); + }); + worker.on('error', (err) => { + // In case of an uncaught exception: Call the callback that was passed to + // `runTask` with the error. + if (worker[kTaskInfo]) + worker[kTaskInfo].done(err, null); + else + this.emit('error', err); + // Remove the worker from the list and start a new Worker to replace the + // current one. + this.workers.splice(this.workers.indexOf(worker), 1); + this.addNewWorker(); + }); + this.workers.push(worker); + this.freeWorkers.push(worker); + this.emit(kWorkerFreedEvent); + } + + runTask(task, callback) { + if (this.freeWorkers.length === 0) { + // No free threads, wait until a worker thread becomes free. + this.tasks.push({ task, callback }); + return; + } + + const worker = this.freeWorkers.pop(); + worker[kTaskInfo] = new WorkerPoolTaskInfo(callback); + worker.postMessage(task); + } + + close() { + for (const worker of this.workers) worker.terminate(); + } +} +``` + +```cjs +const { AsyncResource } = require('node:async_hooks'); +const { EventEmitter } = require('node:events'); +const path = require('node:path'); +const { Worker } = require('node:worker_threads'); + +const kTaskInfo = Symbol('kTaskInfo'); +const kWorkerFreedEvent = Symbol('kWorkerFreedEvent'); + +class WorkerPoolTaskInfo extends AsyncResource { + constructor(callback) { + super('WorkerPoolTaskInfo'); + this.callback = callback; + } + + done(err, result) { + this.runInAsyncScope(this.callback, null, err, result); + this.emitDestroy(); // `TaskInfo`s are used only once. + } +} + +class WorkerPool extends EventEmitter { + constructor(numThreads) { + super(); + this.numThreads = numThreads; + this.workers = []; + this.freeWorkers = []; + this.tasks = []; + + for (let i = 0; i < numThreads; i++) + this.addNewWorker(); + + // Any time the kWorkerFreedEvent is emitted, dispatch + // the next task pending in the queue, if any. + this.on(kWorkerFreedEvent, () => { + if (this.tasks.length > 0) { + const { task, callback } = this.tasks.shift(); + this.runTask(task, callback); + } + }); + } + + addNewWorker() { + const worker = new Worker(path.resolve(__dirname, 'task_processor.js')); + worker.on('message', (result) => { + // In case of success: Call the callback that was passed to `runTask`, + // remove the `TaskInfo` associated with the Worker, and mark it as free + // again. + worker[kTaskInfo].done(null, result); + worker[kTaskInfo] = null; + this.freeWorkers.push(worker); + this.emit(kWorkerFreedEvent); + }); + worker.on('error', (err) => { + // In case of an uncaught exception: Call the callback that was passed to + // `runTask` with the error. + if (worker[kTaskInfo]) + worker[kTaskInfo].done(err, null); + else + this.emit('error', err); + // Remove the worker from the list and start a new Worker to replace the + // current one. + this.workers.splice(this.workers.indexOf(worker), 1); + this.addNewWorker(); + }); + this.workers.push(worker); + this.freeWorkers.push(worker); + this.emit(kWorkerFreedEvent); + } + + runTask(task, callback) { + if (this.freeWorkers.length === 0) { + // No free threads, wait until a worker thread becomes free. + this.tasks.push({ task, callback }); + return; + } + + const worker = this.freeWorkers.pop(); + worker[kTaskInfo] = new WorkerPoolTaskInfo(callback); + worker.postMessage(task); + } + + close() { + for (const worker of this.workers) worker.terminate(); + } +} + +module.exports = WorkerPool; +``` + +Without the explicit tracking added by the `WorkerPoolTaskInfo` objects, +it would appear that the callbacks are associated with the individual `Worker` +objects. However, the creation of the `Worker`s is not associated with the +creation of the tasks and does not provide information about when tasks +were scheduled. + +This pool could be used as follows: + +```mjs +import WorkerPool from './worker_pool.js'; +import os from 'node:os'; + +const pool = new WorkerPool(os.availableParallelism()); + +let finished = 0; +for (let i = 0; i < 10; i++) { + pool.runTask({ a: 42, b: 100 }, (err, result) => { + console.log(i, err, result); + if (++finished === 10) + pool.close(); + }); +} +``` + +```cjs +const WorkerPool = require('./worker_pool.js'); +const os = require('node:os'); + +const pool = new WorkerPool(os.availableParallelism()); + +let finished = 0; +for (let i = 0; i < 10; i++) { + pool.runTask({ a: 42, b: 100 }, (err, result) => { + console.log(i, err, result); + if (++finished === 10) + pool.close(); + }); +} +``` + +### Integrating `AsyncResource` with `EventEmitter` + +Event listeners triggered by an [`EventEmitter`][] may be run in a different +execution context than the one that was active when `eventEmitter.on()` was +called. + +The following example shows how to use the `AsyncResource` class to properly +associate an event listener with the correct execution context. The same +approach can be applied to a [`Stream`][] or a similar event-driven class. + +```mjs +import { createServer } from 'node:http'; +import { AsyncResource, executionAsyncId } from 'node:async_hooks'; + +const server = createServer((req, res) => { + req.on('close', AsyncResource.bind(() => { + // Execution context is bound to the current outer scope. + })); + req.on('close', () => { + // Execution context is bound to the scope that caused 'close' to emit. + }); + res.end(); +}).listen(3000); +``` + +```cjs +const { createServer } = require('node:http'); +const { AsyncResource, executionAsyncId } = require('node:async_hooks'); + +const server = createServer((req, res) => { + req.on('close', AsyncResource.bind(() => { + // Execution context is bound to the current outer scope. + })); + req.on('close', () => { + // Execution context is bound to the scope that caused 'close' to emit. + }); + res.end(); +}).listen(3000); +``` + +[`AsyncResource`]: #class-asyncresource +[`EventEmitter`]: events.md#class-eventemitter +[`Stream`]: stream.md#stream +[`Worker`]: worker_threads.md#class-worker +[`util.promisify()`]: util.md#utilpromisifyoriginal diff --git a/src/generators/json-simple/index.mjs b/src/generators/json-simple/index.mjs index 84abaf0..938e190 100644 --- a/src/generators/json-simple/index.mjs +++ b/src/generators/json-simple/index.mjs @@ -7,7 +7,6 @@ import { remove } from 'unist-util-remove'; import createQueries from '../../queries.mjs'; import { getRemark } from '../../utils/remark.mjs'; -import { createProgressBar } from '../../utils/progressBar.mjs'; /** * This generator generates a simplified JSON version of the API docs and returns it as a string @@ -34,10 +33,6 @@ export default { // Gets a remark processor for stringifying the AST tree into JSON const remarkProcessor = getRemark(); - // Creates a progress bar for the JSON generation - const progressBar = createProgressBar('Generating JSON'); - progressBar.start(input.length, 0); - // Iterates the input (ApiDocMetadataEntry) and performs a few changes const mappedInput = input.map(node => { // Deep clones the content nodes to avoid affecting upstream nodes @@ -53,13 +48,9 @@ export default { // For the JSON generate we want to transform the whole content into JSON content.toJSON = () => remarkProcessor.stringify(content); - progressBar.increment(); - return { ...node, content }; }); - progressBar.stop(); - // This simply grabs all the different files and stringifies them const stringifiedContent = JSON.stringify(mappedInput); diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index 16b4aae..d36e990 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -11,7 +11,6 @@ import tableOfContents from './utils/tableOfContents.mjs'; import { groupNodesByModule } from '../../utils/generators.mjs'; import { getRemarkRehype } from '../../utils/remark.mjs'; -import { createProgressBar } from '../../utils/progressBar.mjs'; /** * @typedef {{ @@ -148,10 +147,6 @@ export default { return replaceTemplateValues(generatedTemplate); }; - // Creates a progress bar to show the progress of the generation process - const progressBar = createProgressBar('Generating HTML files'); - progressBar.start(headNodes.length, 0); - for (const node of headNodes) { const result = processModuleNodes(node); @@ -162,12 +157,8 @@ export default { }); await writeFile(join(output, `${node.api}.html`), minified); - progressBar.increment(); } - // Stops the progress bar and clears the line - progressBar.stop(); - // Define the output folder for API docs assets const assetsFolder = join(output, 'assets'); diff --git a/src/loader.mjs b/src/loader.mjs index 3bedc24..c674f14 100644 --- a/src/loader.mjs +++ b/src/loader.mjs @@ -6,7 +6,7 @@ import { extname } from 'node:path'; import { globSync } from 'glob'; import { VFile } from 'vfile'; -import { createProgressBar } from './utils/progressBar.mjs'; +import createProgressBar from './utils/progressBar.mjs'; /** * This method creates a simple abstract "Loader", which technically diff --git a/src/parser.mjs b/src/parser.mjs index 3c05b64..56fe7fe 100644 --- a/src/parser.mjs +++ b/src/parser.mjs @@ -11,6 +11,7 @@ import createQueries from './queries.mjs'; import { getRemark } from './utils/remark.mjs'; import { createNodeSlugger } from './utils/slugger.mjs'; +import createProgressBar from './utils/progressBar.mjs'; /** * Creates an API doc parser for a given Markdown API doc file @@ -177,7 +178,18 @@ const createParser = () => { const parseApiDocs = async apiDocs => { // We do a Promise.all, to ensure that each API doc is resolved asynchronously // but all need to be resolved first before we return the result to the caller - const resolvedApiDocEntries = await Promise.all(apiDocs.map(parseApiDoc)); + + const progressBar = createProgressBar('Parsing API Docs'); + progressBar.start(apiDocs.length, 0); + + const resolvedApiDocEntries = await Promise.all( + apiDocs.map(async apiDoc => { + progressBar.increment(); + return await parseApiDoc(apiDoc); + }) + ); + + progressBar.stop(); return resolvedApiDocEntries.flat(); }; diff --git a/src/utils/generators.mjs b/src/utils/generators.mjs index c2df3d4..b4bbc1d 100644 --- a/src/utils/generators.mjs +++ b/src/utils/generators.mjs @@ -2,6 +2,8 @@ import { coerce } from 'semver'; +import createProgressBar from './progressBar.mjs'; + /** * Groups all the API metadata nodes by module (`api` property) so that we can process each different file * based on the module it belongs to. @@ -12,14 +14,20 @@ export const groupNodesByModule = nodes => { /** @type {Map>} */ const groupedNodes = new Map(); + const progressBar = createProgressBar(groupNodesByModule.name); + progressBar.start(nodes.length, 0); + for (const node of nodes) { if (!groupedNodes.has(node.api)) { groupedNodes.set(node.api, []); } groupedNodes.get(node.api).push(node); + progressBar.increment(); } + progressBar.stop(); + return groupedNodes; }; diff --git a/src/utils/progressBar.mjs b/src/utils/progressBar.mjs index 8b55763..c05b9e9 100644 --- a/src/utils/progressBar.mjs +++ b/src/utils/progressBar.mjs @@ -3,11 +3,14 @@ import { styleText } from 'node:util'; import cliProgress from 'cli-progress'; /** + * + * Create a progress bar instance + * with our custom format * * @param {string} label * @returns {import('cli-progress').SingleBar} */ -export function createProgressBar(label = '') { +function createProgressBar(label = '') { const format = ` ${styleText(['bold', 'green'], '{bar}')} | ${label} {percentage}% | {value}/{total}`; return new cliProgress.SingleBar({ @@ -17,3 +20,5 @@ export function createProgressBar(label = '') { hideCursor: true, }); } + +export default createProgressBar; diff --git a/src/utils/tests/progressBar.test.mjs b/src/utils/tests/progressBar.test.mjs index ee8320d..f00e10c 100644 --- a/src/utils/tests/progressBar.test.mjs +++ b/src/utils/tests/progressBar.test.mjs @@ -3,7 +3,7 @@ import { test } from 'node:test'; import cliProgress from 'cli-progress'; -import { createProgressBar } from '../progressBar.mjs'; +import createProgressBar from '../progressBar.mjs'; /** * Simple test to unsure that the progress bar is created From 4701dcbdd1bc583bc441f71e1f269c26c46431fb Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Tue, 22 Oct 2024 10:19:21 +0200 Subject: [PATCH 9/9] remove --- fixtures/addons.md | 1387 -------------------- fixtures/assert.md | 2587 ------------------------------------- fixtures/async_context.md | 888 ------------- 3 files changed, 4862 deletions(-) delete mode 100644 fixtures/addons.md delete mode 100644 fixtures/assert.md delete mode 100644 fixtures/async_context.md diff --git a/fixtures/addons.md b/fixtures/addons.md deleted file mode 100644 index 446a63b..0000000 --- a/fixtures/addons.md +++ /dev/null @@ -1,1387 +0,0 @@ -# C++ addons - - - - - -_Addons_ are dynamically-linked shared objects written in C++. The -[`require()`][require] function can load addons as ordinary Node.js modules. -Addons provide an interface between JavaScript and C/C++ libraries. - -There are three options for implementing addons: Node-API, nan, or direct -use of internal V8, libuv, and Node.js libraries. Unless there is a need for -direct access to functionality which is not exposed by Node-API, use Node-API. -Refer to [C/C++ addons with Node-API](n-api.md) for more information on -Node-API. - -When not using Node-API, implementing addons is complicated, -involving knowledge of several components and APIs: - -* [V8][]: the C++ library Node.js uses to provide the - JavaScript implementation. V8 provides the mechanisms for creating objects, - calling functions, etc. V8's API is documented mostly in the - `v8.h` header file (`deps/v8/include/v8.h` in the Node.js source - tree), which is also available [online][v8-docs]. - -* [libuv][]: The C library that implements the Node.js event loop, its worker - threads and all of the asynchronous behaviors of the platform. It also - serves as a cross-platform abstraction library, giving easy, POSIX-like - access across all major operating systems to many common system tasks, such - as interacting with the file system, sockets, timers, and system events. libuv - also provides a threading abstraction similar to POSIX threads for - more sophisticated asynchronous addons that need to move beyond the - standard event loop. Addon authors should - avoid blocking the event loop with I/O or other time-intensive tasks by - offloading work via libuv to non-blocking system operations, worker threads, - or a custom use of libuv threads. - -* Internal Node.js libraries. Node.js itself exports C++ APIs that addons can - use, the most important of which is the `node::ObjectWrap` class. - -* Node.js includes other statically linked libraries including OpenSSL. These - other libraries are located in the `deps/` directory in the Node.js source - tree. Only the libuv, OpenSSL, V8, and zlib symbols are purposefully - re-exported by Node.js and may be used to various extents by addons. See - [Linking to libraries included with Node.js][] for additional information. - -All of the following examples are available for [download][] and may -be used as the starting-point for an addon. - -## Hello world - -This "Hello world" example is a simple addon, written in C++, that is the -equivalent of the following JavaScript code: - -```js -module.exports.hello = () => 'world'; -``` - -First, create the file `hello.cc`: - -```cpp -// hello.cc -#include - -namespace demo { - -using v8::FunctionCallbackInfo; -using v8::Isolate; -using v8::Local; -using v8::Object; -using v8::String; -using v8::Value; - -void Method(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - args.GetReturnValue().Set(String::NewFromUtf8( - isolate, "world").ToLocalChecked()); -} - -void Initialize(Local exports) { - NODE_SET_METHOD(exports, "hello", Method); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) - -} // namespace demo -``` - -All Node.js addons must export an initialization function following -the pattern: - -```cpp -void Initialize(Local exports); -NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) -``` - -There is no semi-colon after `NODE_MODULE` as it's not a function (see -`node.h`). - -The `module_name` must match the filename of the final binary (excluding -the `.node` suffix). - -In the `hello.cc` example, then, the initialization function is `Initialize` -and the addon module name is `addon`. - -When building addons with `node-gyp`, using the macro `NODE_GYP_MODULE_NAME` as -the first parameter of `NODE_MODULE()` will ensure that the name of the final -binary will be passed to `NODE_MODULE()`. - -Addons defined with `NODE_MODULE()` can not be loaded in multiple contexts or -multiple threads at the same time. - -### Context-aware addons - -There are environments in which Node.js addons may need to be loaded multiple -times in multiple contexts. For example, the [Electron][] runtime runs multiple -instances of Node.js in a single process. Each instance will have its own -`require()` cache, and thus each instance will need a native addon to behave -correctly when loaded via `require()`. This means that the addon -must support multiple initializations. - -A context-aware addon can be constructed by using the macro -`NODE_MODULE_INITIALIZER`, which expands to the name of a function which Node.js -will expect to find when it loads an addon. An addon can thus be initialized as -in the following example: - -```cpp -using namespace v8; - -extern "C" NODE_MODULE_EXPORT void -NODE_MODULE_INITIALIZER(Local exports, - Local module, - Local context) { - /* Perform addon initialization steps here. */ -} -``` - -Another option is to use the macro `NODE_MODULE_INIT()`, which will also -construct a context-aware addon. Unlike `NODE_MODULE()`, which is used to -construct an addon around a given addon initializer function, -`NODE_MODULE_INIT()` serves as the declaration of such an initializer to be -followed by a function body. - -The following three variables may be used inside the function body following an -invocation of `NODE_MODULE_INIT()`: - -* `Local exports`, -* `Local module`, and -* `Local context` - -The choice to build a context-aware addon carries with it the responsibility of -carefully managing global static data. Since the addon may be loaded multiple -times, potentially even from different threads, any global static data stored -in the addon must be properly protected, and must not contain any persistent -references to JavaScript objects. The reason for this is that JavaScript -objects are only valid in one context, and will likely cause a crash when -accessed from the wrong context or from a different thread than the one on which -they were created. - -The context-aware addon can be structured to avoid global static data by -performing the following steps: - -* Define a class which will hold per-addon-instance data and which has a static - member of the form - ```cpp - static void DeleteInstance(void* data) { - // Cast `data` to an instance of the class and delete it. - } - ``` -* Heap-allocate an instance of this class in the addon initializer. This can be - accomplished using the `new` keyword. -* Call `node::AddEnvironmentCleanupHook()`, passing it the above-created - instance and a pointer to `DeleteInstance()`. This will ensure the instance is - deleted when the environment is torn down. -* Store the instance of the class in a `v8::External`, and -* Pass the `v8::External` to all methods exposed to JavaScript by passing it - to `v8::FunctionTemplate::New()` or `v8::Function::New()` which creates the - native-backed JavaScript functions. The third parameter of - `v8::FunctionTemplate::New()` or `v8::Function::New()` accepts the - `v8::External` and makes it available in the native callback using the - `v8::FunctionCallbackInfo::Data()` method. - -This will ensure that the per-addon-instance data reaches each binding that can -be called from JavaScript. The per-addon-instance data must also be passed into -any asynchronous callbacks the addon may create. - -The following example illustrates the implementation of a context-aware addon: - -```cpp -#include - -using namespace v8; - -class AddonData { - public: - explicit AddonData(Isolate* isolate): - call_count(0) { - // Ensure this per-addon-instance data is deleted at environment cleanup. - node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this); - } - - // Per-addon data. - int call_count; - - static void DeleteInstance(void* data) { - delete static_cast(data); - } -}; - -static void Method(const v8::FunctionCallbackInfo& info) { - // Retrieve the per-addon-instance data. - AddonData* data = - reinterpret_cast(info.Data().As()->Value()); - data->call_count++; - info.GetReturnValue().Set((double)data->call_count); -} - -// Initialize this addon to be context-aware. -NODE_MODULE_INIT(/* exports, module, context */) { - Isolate* isolate = context->GetIsolate(); - - // Create a new instance of `AddonData` for this instance of the addon and - // tie its life cycle to that of the Node.js environment. - AddonData* data = new AddonData(isolate); - - // Wrap the data in a `v8::External` so we can pass it to the method we - // expose. - Local external = External::New(isolate, data); - - // Expose the method `Method` to JavaScript, and make sure it receives the - // per-addon-instance data we created above by passing `external` as the - // third parameter to the `FunctionTemplate` constructor. - exports->Set(context, - String::NewFromUtf8(isolate, "method").ToLocalChecked(), - FunctionTemplate::New(isolate, Method, external) - ->GetFunction(context).ToLocalChecked()).FromJust(); -} -``` - -#### Worker support - - - -In order to be loaded from multiple Node.js environments, -such as a main thread and a Worker thread, an add-on needs to either: - -* Be an Node-API addon, or -* Be declared as context-aware using `NODE_MODULE_INIT()` as described above - -In order to support [`Worker`][] threads, addons need to clean up any resources -they may have allocated when such a thread exists. This can be achieved through -the usage of the `AddEnvironmentCleanupHook()` function: - -```cpp -void AddEnvironmentCleanupHook(v8::Isolate* isolate, - void (*fun)(void* arg), - void* arg); -``` - -This function adds a hook that will run before a given Node.js instance shuts -down. If necessary, such hooks can be removed before they are run using -`RemoveEnvironmentCleanupHook()`, which has the same signature. Callbacks are -run in last-in first-out order. - -If necessary, there is an additional pair of `AddEnvironmentCleanupHook()` -and `RemoveEnvironmentCleanupHook()` overloads, where the cleanup hook takes a -callback function. This can be used for shutting down asynchronous resources, -such as any libuv handles registered by the addon. - -The following `addon.cc` uses `AddEnvironmentCleanupHook`: - -```cpp -// addon.cc -#include -#include -#include - -using node::AddEnvironmentCleanupHook; -using v8::HandleScope; -using v8::Isolate; -using v8::Local; -using v8::Object; - -// Note: In a real-world application, do not rely on static/global data. -static char cookie[] = "yum yum"; -static int cleanup_cb1_called = 0; -static int cleanup_cb2_called = 0; - -static void cleanup_cb1(void* arg) { - Isolate* isolate = static_cast(arg); - HandleScope scope(isolate); - Local obj = Object::New(isolate); - assert(!obj.IsEmpty()); // assert VM is still alive - assert(obj->IsObject()); - cleanup_cb1_called++; -} - -static void cleanup_cb2(void* arg) { - assert(arg == static_cast(cookie)); - cleanup_cb2_called++; -} - -static void sanity_check(void*) { - assert(cleanup_cb1_called == 1); - assert(cleanup_cb2_called == 1); -} - -// Initialize this addon to be context-aware. -NODE_MODULE_INIT(/* exports, module, context */) { - Isolate* isolate = context->GetIsolate(); - - AddEnvironmentCleanupHook(isolate, sanity_check, nullptr); - AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie); - AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate); -} -``` - -Test in JavaScript by running: - -```js -// test.js -require('./build/Release/addon'); -``` - -### Building - -Once the source code has been written, it must be compiled into the binary -`addon.node` file. To do so, create a file called `binding.gyp` in the -top-level of the project describing the build configuration of the module -using a JSON-like format. This file is used by [node-gyp][], a tool written -specifically to compile Node.js addons. - -```json -{ - "targets": [ - { - "target_name": "addon", - "sources": [ "hello.cc" ] - } - ] -} -``` - -A version of the `node-gyp` utility is bundled and distributed with -Node.js as part of `npm`. This version is not made directly available for -developers to use and is intended only to support the ability to use the -`npm install` command to compile and install addons. Developers who wish to -use `node-gyp` directly can install it using the command -`npm install -g node-gyp`. See the `node-gyp` [installation instructions][] for -more information, including platform-specific requirements. - -Once the `binding.gyp` file has been created, use `node-gyp configure` to -generate the appropriate project build files for the current platform. This -will generate either a `Makefile` (on Unix platforms) or a `vcxproj` file -(on Windows) in the `build/` directory. - -Next, invoke the `node-gyp build` command to generate the compiled `addon.node` -file. This will be put into the `build/Release/` directory. - -When using `npm install` to install a Node.js addon, npm uses its own bundled -version of `node-gyp` to perform this same set of actions, generating a -compiled version of the addon for the user's platform on demand. - -Once built, the binary addon can be used from within Node.js by pointing -[`require()`][require] to the built `addon.node` module: - -```js -// hello.js -const addon = require('./build/Release/addon'); - -console.log(addon.hello()); -// Prints: 'world' -``` - -Because the exact path to the compiled addon binary can vary depending on how -it is compiled (i.e. sometimes it may be in `./build/Debug/`), addons can use -the [bindings][] package to load the compiled module. - -While the `bindings` package implementation is more sophisticated in how it -locates addon modules, it is essentially using a `try…catch` pattern similar to: - -```js -try { - return require('./build/Release/addon.node'); -} catch (err) { - return require('./build/Debug/addon.node'); -} -``` - -### Linking to libraries included with Node.js - -Node.js uses statically linked libraries such as V8, libuv, and OpenSSL. All -addons are required to link to V8 and may link to any of the other dependencies -as well. Typically, this is as simple as including the appropriate -`#include <...>` statements (e.g. `#include `) and `node-gyp` will locate -the appropriate headers automatically. However, there are a few caveats to be -aware of: - -* When `node-gyp` runs, it will detect the specific release version of Node.js - and download either the full source tarball or just the headers. If the full - source is downloaded, addons will have complete access to the full set of - Node.js dependencies. However, if only the Node.js headers are downloaded, - then only the symbols exported by Node.js will be available. - -* `node-gyp` can be run using the `--nodedir` flag pointing at a local Node.js - source image. Using this option, the addon will have access to the full set of - dependencies. - -### Loading addons using `require()` - -The filename extension of the compiled addon binary is `.node` (as opposed -to `.dll` or `.so`). The [`require()`][require] function is written to look for -files with the `.node` file extension and initialize those as dynamically-linked -libraries. - -When calling [`require()`][require], the `.node` extension can usually be -omitted and Node.js will still find and initialize the addon. One caveat, -however, is that Node.js will first attempt to locate and load modules or -JavaScript files that happen to share the same base name. For instance, if -there is a file `addon.js` in the same directory as the binary `addon.node`, -then [`require('addon')`][require] will give precedence to the `addon.js` file -and load it instead. - -## Native abstractions for Node.js - -Each of the examples illustrated in this document directly use the -Node.js and V8 APIs for implementing addons. The V8 API can, and has, changed -dramatically from one V8 release to the next (and one major Node.js release to -the next). With each change, addons may need to be updated and recompiled in -order to continue functioning. The Node.js release schedule is designed to -minimize the frequency and impact of such changes but there is little that -Node.js can do to ensure stability of the V8 APIs. - -The [Native Abstractions for Node.js][] (or `nan`) provide a set of tools that -addon developers are recommended to use to keep compatibility between past and -future releases of V8 and Node.js. See the `nan` [examples][] for an -illustration of how it can be used. - -## Node-API - -> Stability: 2 - Stable - -Node-API is an API for building native addons. It is independent from -the underlying JavaScript runtime (e.g. V8) and is maintained as part of -Node.js itself. This API will be Application Binary Interface (ABI) stable -across versions of Node.js. It is intended to insulate addons from -changes in the underlying JavaScript engine and allow modules -compiled for one version to run on later versions of Node.js without -recompilation. Addons are built/packaged with the same approach/tools -outlined in this document (node-gyp, etc.). The only difference is the -set of APIs that are used by the native code. Instead of using the V8 -or [Native Abstractions for Node.js][] APIs, the functions available -in the Node-API are used. - -Creating and maintaining an addon that benefits from the ABI stability -provided by Node-API carries with it certain -[implementation considerations][]. - -To use Node-API in the above "Hello world" example, replace the content of -`hello.cc` with the following. All other instructions remain the same. - -```cpp -// hello.cc using Node-API -#include - -namespace demo { - -napi_value Method(napi_env env, napi_callback_info args) { - napi_value greeting; - napi_status status; - - status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting); - if (status != napi_ok) return nullptr; - return greeting; -} - -napi_value init(napi_env env, napi_value exports) { - napi_status status; - napi_value fn; - - status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn); - if (status != napi_ok) return nullptr; - - status = napi_set_named_property(env, exports, "hello", fn); - if (status != napi_ok) return nullptr; - return exports; -} - -NAPI_MODULE(NODE_GYP_MODULE_NAME, init) - -} // namespace demo -``` - -The functions available and how to use them are documented in -[C/C++ addons with Node-API](n-api.md). - -## Addon examples - -Following are some example addons intended to help developers get started. The -examples use the V8 APIs. Refer to the online [V8 reference][v8-docs] -for help with the various V8 calls, and V8's [Embedder's Guide][] for an -explanation of several concepts used such as handles, scopes, function -templates, etc. - -Each of these examples using the following `binding.gyp` file: - -```json -{ - "targets": [ - { - "target_name": "addon", - "sources": [ "addon.cc" ] - } - ] -} -``` - -In cases where there is more than one `.cc` file, simply add the additional -filename to the `sources` array: - -```json -"sources": ["addon.cc", "myexample.cc"] -``` - -Once the `binding.gyp` file is ready, the example addons can be configured and -built using `node-gyp`: - -```bash -node-gyp configure build -``` - -### Function arguments - -Addons will typically expose objects and functions that can be accessed from -JavaScript running within Node.js. When functions are invoked from JavaScript, -the input arguments and return value must be mapped to and from the C/C++ -code. - -The following example illustrates how to read function arguments passed from -JavaScript and how to return a result: - -```cpp -// addon.cc -#include - -namespace demo { - -using v8::Exception; -using v8::FunctionCallbackInfo; -using v8::Isolate; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::String; -using v8::Value; - -// This is the implementation of the "add" method -// Input arguments are passed using the -// const FunctionCallbackInfo& args struct -void Add(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - - // Check the number of arguments passed. - if (args.Length() < 2) { - // Throw an Error that is passed back to JavaScript - isolate->ThrowException(Exception::TypeError( - String::NewFromUtf8(isolate, - "Wrong number of arguments").ToLocalChecked())); - return; - } - - // Check the argument types - if (!args[0]->IsNumber() || !args[1]->IsNumber()) { - isolate->ThrowException(Exception::TypeError( - String::NewFromUtf8(isolate, - "Wrong arguments").ToLocalChecked())); - return; - } - - // Perform the operation - double value = - args[0].As()->Value() + args[1].As()->Value(); - Local num = Number::New(isolate, value); - - // Set the return value (using the passed in - // FunctionCallbackInfo&) - args.GetReturnValue().Set(num); -} - -void Init(Local exports) { - NODE_SET_METHOD(exports, "add", Add); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, Init) - -} // namespace demo -``` - -Once compiled, the example addon can be required and used from within Node.js: - -```js -// test.js -const addon = require('./build/Release/addon'); - -console.log('This should be eight:', addon.add(3, 5)); -``` - -### Callbacks - -It is common practice within addons to pass JavaScript functions to a C++ -function and execute them from there. The following example illustrates how -to invoke such callbacks: - -```cpp -// addon.cc -#include - -namespace demo { - -using v8::Context; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::Isolate; -using v8::Local; -using v8::Null; -using v8::Object; -using v8::String; -using v8::Value; - -void RunCallback(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - Local cb = Local::Cast(args[0]); - const unsigned argc = 1; - Local argv[argc] = { - String::NewFromUtf8(isolate, - "hello world").ToLocalChecked() }; - cb->Call(context, Null(isolate), argc, argv).ToLocalChecked(); -} - -void Init(Local exports, Local module) { - NODE_SET_METHOD(module, "exports", RunCallback); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, Init) - -} // namespace demo -``` - -This example uses a two-argument form of `Init()` that receives the full -`module` object as the second argument. This allows the addon to completely -overwrite `exports` with a single function instead of adding the function as a -property of `exports`. - -To test it, run the following JavaScript: - -```js -// test.js -const addon = require('./build/Release/addon'); - -addon((msg) => { - console.log(msg); -// Prints: 'hello world' -}); -``` - -In this example, the callback function is invoked synchronously. - -### Object factory - -Addons can create and return new objects from within a C++ function as -illustrated in the following example. An object is created and returned with a -property `msg` that echoes the string passed to `createObject()`: - -```cpp -// addon.cc -#include - -namespace demo { - -using v8::Context; -using v8::FunctionCallbackInfo; -using v8::Isolate; -using v8::Local; -using v8::Object; -using v8::String; -using v8::Value; - -void CreateObject(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - - Local obj = Object::New(isolate); - obj->Set(context, - String::NewFromUtf8(isolate, - "msg").ToLocalChecked(), - args[0]->ToString(context).ToLocalChecked()) - .FromJust(); - - args.GetReturnValue().Set(obj); -} - -void Init(Local exports, Local module) { - NODE_SET_METHOD(module, "exports", CreateObject); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, Init) - -} // namespace demo -``` - -To test it in JavaScript: - -```js -// test.js -const addon = require('./build/Release/addon'); - -const obj1 = addon('hello'); -const obj2 = addon('world'); -console.log(obj1.msg, obj2.msg); -// Prints: 'hello world' -``` - -### Function factory - -Another common scenario is creating JavaScript functions that wrap C++ -functions and returning those back to JavaScript: - -```cpp -// addon.cc -#include - -namespace demo { - -using v8::Context; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::Isolate; -using v8::Local; -using v8::Object; -using v8::String; -using v8::Value; - -void MyFunction(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - args.GetReturnValue().Set(String::NewFromUtf8( - isolate, "hello world").ToLocalChecked()); -} - -void CreateFunction(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - - Local context = isolate->GetCurrentContext(); - Local tpl = FunctionTemplate::New(isolate, MyFunction); - Local fn = tpl->GetFunction(context).ToLocalChecked(); - - // omit this to make it anonymous - fn->SetName(String::NewFromUtf8( - isolate, "theFunction").ToLocalChecked()); - - args.GetReturnValue().Set(fn); -} - -void Init(Local exports, Local module) { - NODE_SET_METHOD(module, "exports", CreateFunction); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, Init) - -} // namespace demo -``` - -To test: - -```js -// test.js -const addon = require('./build/Release/addon'); - -const fn = addon(); -console.log(fn()); -// Prints: 'hello world' -``` - -### Wrapping C++ objects - -It is also possible to wrap C++ objects/classes in a way that allows new -instances to be created using the JavaScript `new` operator: - -```cpp -// addon.cc -#include -#include "myobject.h" - -namespace demo { - -using v8::Local; -using v8::Object; - -void InitAll(Local exports) { - MyObject::Init(exports); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll) - -} // namespace demo -``` - -Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`: - -```cpp -// myobject.h -#ifndef MYOBJECT_H -#define MYOBJECT_H - -#include -#include - -namespace demo { - -class MyObject : public node::ObjectWrap { - public: - static void Init(v8::Local exports); - - private: - explicit MyObject(double value = 0); - ~MyObject(); - - static void New(const v8::FunctionCallbackInfo& args); - static void PlusOne(const v8::FunctionCallbackInfo& args); - - double value_; -}; - -} // namespace demo - -#endif -``` - -In `myobject.cc`, implement the various methods that are to be exposed. -In the following code, the method `plusOne()` is exposed by adding it to the -constructor's prototype: - -```cpp -// myobject.cc -#include "myobject.h" - -namespace demo { - -using v8::Context; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::Isolate; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::ObjectTemplate; -using v8::String; -using v8::Value; - -MyObject::MyObject(double value) : value_(value) { -} - -MyObject::~MyObject() { -} - -void MyObject::Init(Local exports) { - Isolate* isolate = exports->GetIsolate(); - Local context = isolate->GetCurrentContext(); - - Local addon_data_tpl = ObjectTemplate::New(isolate); - addon_data_tpl->SetInternalFieldCount(1); // 1 field for the MyObject::New() - Local addon_data = - addon_data_tpl->NewInstance(context).ToLocalChecked(); - - // Prepare constructor template - Local tpl = FunctionTemplate::New(isolate, New, addon_data); - tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - // Prototype - NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); - - Local constructor = tpl->GetFunction(context).ToLocalChecked(); - addon_data->SetInternalField(0, constructor); - exports->Set(context, String::NewFromUtf8( - isolate, "MyObject").ToLocalChecked(), - constructor).FromJust(); -} - -void MyObject::New(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - - if (args.IsConstructCall()) { - // Invoked as constructor: `new MyObject(...)` - double value = args[0]->IsUndefined() ? - 0 : args[0]->NumberValue(context).FromMaybe(0); - MyObject* obj = new MyObject(value); - obj->Wrap(args.This()); - args.GetReturnValue().Set(args.This()); - } else { - // Invoked as plain function `MyObject(...)`, turn into construct call. - const int argc = 1; - Local argv[argc] = { args[0] }; - Local cons = - args.Data().As()->GetInternalField(0) - .As().As(); - Local result = - cons->NewInstance(context, argc, argv).ToLocalChecked(); - args.GetReturnValue().Set(result); - } -} - -void MyObject::PlusOne(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - - MyObject* obj = ObjectWrap::Unwrap(args.This()); - obj->value_ += 1; - - args.GetReturnValue().Set(Number::New(isolate, obj->value_)); -} - -} // namespace demo -``` - -To build this example, the `myobject.cc` file must be added to the -`binding.gyp`: - -```json -{ - "targets": [ - { - "target_name": "addon", - "sources": [ - "addon.cc", - "myobject.cc" - ] - } - ] -} -``` - -Test it with: - -```js -// test.js -const addon = require('./build/Release/addon'); - -const obj = new addon.MyObject(10); -console.log(obj.plusOne()); -// Prints: 11 -console.log(obj.plusOne()); -// Prints: 12 -console.log(obj.plusOne()); -// Prints: 13 -``` - -The destructor for a wrapper object will run when the object is -garbage-collected. For destructor testing, there are command-line flags that -can be used to make it possible to force garbage collection. These flags are -provided by the underlying V8 JavaScript engine. They are subject to change -or removal at any time. They are not documented by Node.js or V8, and they -should never be used outside of testing. - -During shutdown of the process or worker threads destructors are not called -by the JS engine. Therefore it's the responsibility of the user to track -these objects and ensure proper destruction to avoid resource leaks. - -### Factory of wrapped objects - -Alternatively, it is possible to use a factory pattern to avoid explicitly -creating object instances using the JavaScript `new` operator: - -```js -const obj = addon.createObject(); -// instead of: -// const obj = new addon.Object(); -``` - -First, the `createObject()` method is implemented in `addon.cc`: - -```cpp -// addon.cc -#include -#include "myobject.h" - -namespace demo { - -using v8::FunctionCallbackInfo; -using v8::Isolate; -using v8::Local; -using v8::Object; -using v8::String; -using v8::Value; - -void CreateObject(const FunctionCallbackInfo& args) { - MyObject::NewInstance(args); -} - -void InitAll(Local exports, Local module) { - MyObject::Init(exports->GetIsolate()); - - NODE_SET_METHOD(module, "exports", CreateObject); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll) - -} // namespace demo -``` - -In `myobject.h`, the static method `NewInstance()` is added to handle -instantiating the object. This method takes the place of using `new` in -JavaScript: - -```cpp -// myobject.h -#ifndef MYOBJECT_H -#define MYOBJECT_H - -#include -#include - -namespace demo { - -class MyObject : public node::ObjectWrap { - public: - static void Init(v8::Isolate* isolate); - static void NewInstance(const v8::FunctionCallbackInfo& args); - - private: - explicit MyObject(double value = 0); - ~MyObject(); - - static void New(const v8::FunctionCallbackInfo& args); - static void PlusOne(const v8::FunctionCallbackInfo& args); - static v8::Global constructor; - double value_; -}; - -} // namespace demo - -#endif -``` - -The implementation in `myobject.cc` is similar to the previous example: - -```cpp -// myobject.cc -#include -#include "myobject.h" - -namespace demo { - -using node::AddEnvironmentCleanupHook; -using v8::Context; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::Global; -using v8::Isolate; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::String; -using v8::Value; - -// Warning! This is not thread-safe, this addon cannot be used for worker -// threads. -Global MyObject::constructor; - -MyObject::MyObject(double value) : value_(value) { -} - -MyObject::~MyObject() { -} - -void MyObject::Init(Isolate* isolate) { - // Prepare constructor template - Local tpl = FunctionTemplate::New(isolate, New); - tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - // Prototype - NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne); - - Local context = isolate->GetCurrentContext(); - constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked()); - - AddEnvironmentCleanupHook(isolate, [](void*) { - constructor.Reset(); - }, nullptr); -} - -void MyObject::New(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - - if (args.IsConstructCall()) { - // Invoked as constructor: `new MyObject(...)` - double value = args[0]->IsUndefined() ? - 0 : args[0]->NumberValue(context).FromMaybe(0); - MyObject* obj = new MyObject(value); - obj->Wrap(args.This()); - args.GetReturnValue().Set(args.This()); - } else { - // Invoked as plain function `MyObject(...)`, turn into construct call. - const int argc = 1; - Local argv[argc] = { args[0] }; - Local cons = Local::New(isolate, constructor); - Local instance = - cons->NewInstance(context, argc, argv).ToLocalChecked(); - args.GetReturnValue().Set(instance); - } -} - -void MyObject::NewInstance(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - - const unsigned argc = 1; - Local argv[argc] = { args[0] }; - Local cons = Local::New(isolate, constructor); - Local context = isolate->GetCurrentContext(); - Local instance = - cons->NewInstance(context, argc, argv).ToLocalChecked(); - - args.GetReturnValue().Set(instance); -} - -void MyObject::PlusOne(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - - MyObject* obj = ObjectWrap::Unwrap(args.This()); - obj->value_ += 1; - - args.GetReturnValue().Set(Number::New(isolate, obj->value_)); -} - -} // namespace demo -``` - -Once again, to build this example, the `myobject.cc` file must be added to the -`binding.gyp`: - -```json -{ - "targets": [ - { - "target_name": "addon", - "sources": [ - "addon.cc", - "myobject.cc" - ] - } - ] -} -``` - -Test it with: - -```js -// test.js -const createObject = require('./build/Release/addon'); - -const obj = createObject(10); -console.log(obj.plusOne()); -// Prints: 11 -console.log(obj.plusOne()); -// Prints: 12 -console.log(obj.plusOne()); -// Prints: 13 - -const obj2 = createObject(20); -console.log(obj2.plusOne()); -// Prints: 21 -console.log(obj2.plusOne()); -// Prints: 22 -console.log(obj2.plusOne()); -// Prints: 23 -``` - -### Passing wrapped objects around - -In addition to wrapping and returning C++ objects, it is possible to pass -wrapped objects around by unwrapping them with the Node.js helper function -`node::ObjectWrap::Unwrap`. The following examples shows a function `add()` -that can take two `MyObject` objects as input arguments: - -```cpp -// addon.cc -#include -#include -#include "myobject.h" - -namespace demo { - -using v8::Context; -using v8::FunctionCallbackInfo; -using v8::Isolate; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::String; -using v8::Value; - -void CreateObject(const FunctionCallbackInfo& args) { - MyObject::NewInstance(args); -} - -void Add(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - - MyObject* obj1 = node::ObjectWrap::Unwrap( - args[0]->ToObject(context).ToLocalChecked()); - MyObject* obj2 = node::ObjectWrap::Unwrap( - args[1]->ToObject(context).ToLocalChecked()); - - double sum = obj1->value() + obj2->value(); - args.GetReturnValue().Set(Number::New(isolate, sum)); -} - -void InitAll(Local exports) { - MyObject::Init(exports->GetIsolate()); - - NODE_SET_METHOD(exports, "createObject", CreateObject); - NODE_SET_METHOD(exports, "add", Add); -} - -NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll) - -} // namespace demo -``` - -In `myobject.h`, a new public method is added to allow access to private values -after unwrapping the object. - -```cpp -// myobject.h -#ifndef MYOBJECT_H -#define MYOBJECT_H - -#include -#include - -namespace demo { - -class MyObject : public node::ObjectWrap { - public: - static void Init(v8::Isolate* isolate); - static void NewInstance(const v8::FunctionCallbackInfo& args); - inline double value() const { return value_; } - - private: - explicit MyObject(double value = 0); - ~MyObject(); - - static void New(const v8::FunctionCallbackInfo& args); - static v8::Global constructor; - double value_; -}; - -} // namespace demo - -#endif -``` - -The implementation of `myobject.cc` is similar to before: - -```cpp -// myobject.cc -#include -#include "myobject.h" - -namespace demo { - -using node::AddEnvironmentCleanupHook; -using v8::Context; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::Global; -using v8::Isolate; -using v8::Local; -using v8::Object; -using v8::String; -using v8::Value; - -// Warning! This is not thread-safe, this addon cannot be used for worker -// threads. -Global MyObject::constructor; - -MyObject::MyObject(double value) : value_(value) { -} - -MyObject::~MyObject() { -} - -void MyObject::Init(Isolate* isolate) { - // Prepare constructor template - Local tpl = FunctionTemplate::New(isolate, New); - tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - Local context = isolate->GetCurrentContext(); - constructor.Reset(isolate, tpl->GetFunction(context).ToLocalChecked()); - - AddEnvironmentCleanupHook(isolate, [](void*) { - constructor.Reset(); - }, nullptr); -} - -void MyObject::New(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - Local context = isolate->GetCurrentContext(); - - if (args.IsConstructCall()) { - // Invoked as constructor: `new MyObject(...)` - double value = args[0]->IsUndefined() ? - 0 : args[0]->NumberValue(context).FromMaybe(0); - MyObject* obj = new MyObject(value); - obj->Wrap(args.This()); - args.GetReturnValue().Set(args.This()); - } else { - // Invoked as plain function `MyObject(...)`, turn into construct call. - const int argc = 1; - Local argv[argc] = { args[0] }; - Local cons = Local::New(isolate, constructor); - Local instance = - cons->NewInstance(context, argc, argv).ToLocalChecked(); - args.GetReturnValue().Set(instance); - } -} - -void MyObject::NewInstance(const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); - - const unsigned argc = 1; - Local argv[argc] = { args[0] }; - Local cons = Local::New(isolate, constructor); - Local context = isolate->GetCurrentContext(); - Local instance = - cons->NewInstance(context, argc, argv).ToLocalChecked(); - - args.GetReturnValue().Set(instance); -} - -} // namespace demo -``` - -Test it with: - -```js -// test.js -const addon = require('./build/Release/addon'); - -const obj1 = addon.createObject(10); -const obj2 = addon.createObject(20); -const result = addon.add(obj1, obj2); - -console.log(result); -// Prints: 30 -``` - -[Electron]: https://electronjs.org/ -[Embedder's Guide]: https://v8.dev/docs/embed -[Linking to libraries included with Node.js]: #linking-to-libraries-included-with-nodejs -[Native Abstractions for Node.js]: https://github.com/nodejs/nan -[V8]: https://v8.dev/ -[`Worker`]: worker_threads.md#class-worker -[bindings]: https://github.com/TooTallNate/node-bindings -[download]: https://github.com/nodejs/node-addon-examples -[examples]: https://github.com/nodejs/nan/tree/HEAD/examples/ -[implementation considerations]: n-api.md#implications-of-abi-stability -[installation instructions]: https://github.com/nodejs/node-gyp#installation -[libuv]: https://github.com/libuv/libuv -[node-gyp]: https://github.com/nodejs/node-gyp -[require]: modules.md#requireid -[v8-docs]: https://v8docs.nodesource.com/ diff --git a/fixtures/assert.md b/fixtures/assert.md deleted file mode 100644 index 70f4ac6..0000000 --- a/fixtures/assert.md +++ /dev/null @@ -1,2587 +0,0 @@ -# Assert - - - -> Stability: 2 - Stable - - - -The `node:assert` module provides a set of assertion functions for verifying -invariants. - -## Strict assertion mode - - - -In strict assertion mode, non-strict methods behave like their corresponding -strict methods. For example, [`assert.deepEqual()`][] will behave like -[`assert.deepStrictEqual()`][]. - -In strict assertion mode, error messages for objects display a diff. In legacy -assertion mode, error messages for objects display the objects, often truncated. - -To use strict assertion mode: - -```mjs -import { strict as assert } from 'node:assert'; -``` - -```cjs -const assert = require('node:assert').strict; -``` - -```mjs -import assert from 'node:assert/strict'; -``` - -```cjs -const assert = require('node:assert/strict'); -``` - -Example error diff: - -```mjs -import { strict as assert } from 'node:assert'; - -assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected ... Lines skipped -// -// [ -// [ -// ... -// 2, -// + 3 -// - '3' -// ], -// ... -// 5 -// ] -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected ... Lines skipped -// -// [ -// [ -// ... -// 2, -// + 3 -// - '3' -// ], -// ... -// 5 -// ] -``` - -To deactivate the colors, use the `NO_COLOR` or `NODE_DISABLE_COLORS` -environment variables. This will also deactivate the colors in the REPL. For -more on color support in terminal environments, read the tty -[`getColorDepth()`][] documentation. - -## Legacy assertion mode - -Legacy assertion mode uses the [`==` operator][] in: - -* [`assert.deepEqual()`][] -* [`assert.equal()`][] -* [`assert.notDeepEqual()`][] -* [`assert.notEqual()`][] - -To use legacy assertion mode: - -```mjs -import assert from 'node:assert'; -``` - -```cjs -const assert = require('node:assert'); -``` - -Legacy assertion mode may have surprising results, especially when using -[`assert.deepEqual()`][]: - -```cjs -// WARNING: This does not throw an AssertionError in legacy assertion mode! -assert.deepEqual(/a/gi, new Date()); -``` - -## Class: assert.AssertionError - -* Extends: {errors.Error} - -Indicates the failure of an assertion. All errors thrown by the `node:assert` -module will be instances of the `AssertionError` class. - -### `new assert.AssertionError(options)` - - - -* `options` {Object} - * `message` {string} If provided, the error message is set to this value. - * `actual` {any} The `actual` property on the error instance. - * `expected` {any} The `expected` property on the error instance. - * `operator` {string} The `operator` property on the error instance. - * `stackStartFn` {Function} If provided, the generated stack trace omits - frames before this function. - -A subclass of `Error` that indicates the failure of an assertion. - -All instances contain the built-in `Error` properties (`message` and `name`) -and: - -* `actual` {any} Set to the `actual` argument for methods such as - [`assert.strictEqual()`][]. -* `expected` {any} Set to the `expected` value for methods such as - [`assert.strictEqual()`][]. -* `generatedMessage` {boolean} Indicates if the message was auto-generated - (`true`) or not. -* `code` {string} Value is always `ERR_ASSERTION` to show that the error is an - assertion error. -* `operator` {string} Set to the passed in operator value. - -```mjs -import assert from 'node:assert'; - -// Generate an AssertionError to compare the error message later: -const { message } = new assert.AssertionError({ - actual: 1, - expected: 2, - operator: 'strictEqual', -}); - -// Verify error output: -try { - assert.strictEqual(1, 2); -} catch (err) { - assert(err instanceof assert.AssertionError); - assert.strictEqual(err.message, message); - assert.strictEqual(err.name, 'AssertionError'); - assert.strictEqual(err.actual, 1); - assert.strictEqual(err.expected, 2); - assert.strictEqual(err.code, 'ERR_ASSERTION'); - assert.strictEqual(err.operator, 'strictEqual'); - assert.strictEqual(err.generatedMessage, true); -} -``` - -```cjs -const assert = require('node:assert'); - -// Generate an AssertionError to compare the error message later: -const { message } = new assert.AssertionError({ - actual: 1, - expected: 2, - operator: 'strictEqual', -}); - -// Verify error output: -try { - assert.strictEqual(1, 2); -} catch (err) { - assert(err instanceof assert.AssertionError); - assert.strictEqual(err.message, message); - assert.strictEqual(err.name, 'AssertionError'); - assert.strictEqual(err.actual, 1); - assert.strictEqual(err.expected, 2); - assert.strictEqual(err.code, 'ERR_ASSERTION'); - assert.strictEqual(err.operator, 'strictEqual'); - assert.strictEqual(err.generatedMessage, true); -} -``` - -## Class: `assert.CallTracker` - - - -> Stability: 0 - Deprecated - -This feature is deprecated and will be removed in a future version. -Please consider using alternatives such as the -[`mock`][] helper function. - -### `new assert.CallTracker()` - - - -Creates a new [`CallTracker`][] object which can be used to track if functions -were called a specific number of times. The `tracker.verify()` must be called -for the verification to take place. The usual pattern would be to call it in a -[`process.on('exit')`][] handler. - -```mjs -import assert from 'node:assert'; -import process from 'node:process'; - -const tracker = new assert.CallTracker(); - -function func() {} - -// callsfunc() must be called exactly 1 time before tracker.verify(). -const callsfunc = tracker.calls(func, 1); - -callsfunc(); - -// Calls tracker.verify() and verifies if all tracker.calls() functions have -// been called exact times. -process.on('exit', () => { - tracker.verify(); -}); -``` - -```cjs -const assert = require('node:assert'); -const process = require('node:process'); - -const tracker = new assert.CallTracker(); - -function func() {} - -// callsfunc() must be called exactly 1 time before tracker.verify(). -const callsfunc = tracker.calls(func, 1); - -callsfunc(); - -// Calls tracker.verify() and verifies if all tracker.calls() functions have -// been called exact times. -process.on('exit', () => { - tracker.verify(); -}); -``` - -### `tracker.calls([fn][, exact])` - - - -* `fn` {Function} **Default:** A no-op function. -* `exact` {number} **Default:** `1`. -* Returns: {Function} A function that wraps `fn`. - -The wrapper function is expected to be called exactly `exact` times. If the -function has not been called exactly `exact` times when -[`tracker.verify()`][] is called, then [`tracker.verify()`][] will throw an -error. - -```mjs -import assert from 'node:assert'; - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func); -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func); -``` - -### `tracker.getCalls(fn)` - - - -* `fn` {Function} - -* Returns: {Array} An array with all the calls to a tracked function. - -* Object {Object} - * `thisArg` {Object} - * `arguments` {Array} the arguments passed to the tracked function - -```mjs -import assert from 'node:assert'; - -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); -callsfunc(1, 2, 3); - -assert.deepStrictEqual(tracker.getCalls(callsfunc), - [{ thisArg: undefined, arguments: [1, 2, 3] }]); -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); -callsfunc(1, 2, 3); - -assert.deepStrictEqual(tracker.getCalls(callsfunc), - [{ thisArg: undefined, arguments: [1, 2, 3] }]); -``` - -### `tracker.report()` - - - -* Returns: {Array} An array of objects containing information about the wrapper - functions returned by [`tracker.calls()`][]. -* Object {Object} - * `message` {string} - * `actual` {number} The actual number of times the function was called. - * `expected` {number} The number of times the function was expected to be - called. - * `operator` {string} The name of the function that is wrapped. - * `stack` {Object} A stack trace of the function. - -The arrays contains information about the expected and actual number of calls of -the functions that have not been called the expected number of times. - -```mjs -import assert from 'node:assert'; - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -// Returns an array containing information on callsfunc() -console.log(tracker.report()); -// [ -// { -// message: 'Expected the func function to be executed 2 time(s) but was -// executed 0 time(s).', -// actual: 0, -// expected: 2, -// operator: 'func', -// stack: stack trace -// } -// ] -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -// Returns an array containing information on callsfunc() -console.log(tracker.report()); -// [ -// { -// message: 'Expected the func function to be executed 2 time(s) but was -// executed 0 time(s).', -// actual: 0, -// expected: 2, -// operator: 'func', -// stack: stack trace -// } -// ] -``` - -### `tracker.reset([fn])` - - - -* `fn` {Function} a tracked function to reset. - -Reset calls of the call tracker. -If a tracked function is passed as an argument, the calls will be reset for it. -If no arguments are passed, all tracked functions will be reset. - -```mjs -import assert from 'node:assert'; - -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); - -callsfunc(); -// Tracker was called once -assert.strictEqual(tracker.getCalls(callsfunc).length, 1); - -tracker.reset(callsfunc); -assert.strictEqual(tracker.getCalls(callsfunc).length, 0); -``` - -```cjs -const assert = require('node:assert'); - -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); - -callsfunc(); -// Tracker was called once -assert.strictEqual(tracker.getCalls(callsfunc).length, 1); - -tracker.reset(callsfunc); -assert.strictEqual(tracker.getCalls(callsfunc).length, 0); -``` - -### `tracker.verify()` - - - -Iterates through the list of functions passed to -[`tracker.calls()`][] and will throw an error for functions that -have not been called the expected number of times. - -```mjs -import assert from 'node:assert'; - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -callsfunc(); - -// Will throw an error since callsfunc() was only called once. -tracker.verify(); -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -callsfunc(); - -// Will throw an error since callsfunc() was only called once. -tracker.verify(); -``` - -## `assert(value[, message])` - - - -* `value` {any} The input that is checked for being truthy. -* `message` {string|Error} - -An alias of [`assert.ok()`][]. - -## `assert.deepEqual(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -**Strict assertion mode** - -An alias of [`assert.deepStrictEqual()`][]. - -**Legacy assertion mode** - -> Stability: 3 - Legacy: Use [`assert.deepStrictEqual()`][] instead. - -Tests for deep equality between the `actual` and `expected` parameters. Consider -using [`assert.deepStrictEqual()`][] instead. [`assert.deepEqual()`][] can have -surprising results. - -_Deep equality_ means that the enumerable "own" properties of child objects -are also recursively evaluated by the following rules. - -### Comparison details - -* Primitive values are compared with the [`==` operator][], - with the exception of `NaN`. It is treated as being identical in case - both sides are `NaN`. -* [Type tags][Object.prototype.toString()] of objects should be the same. -* Only [enumerable "own" properties][] are considered. -* [`Error`][] names, messages, causes, and errors are always compared, - even if these are not enumerable properties. -* [Object wrappers][] are compared both as objects and unwrapped values. -* `Object` properties are compared unordered. -* [`Map`][] keys and [`Set`][] items are compared unordered. -* Recursion stops when both sides differ or both sides encounter a circular - reference. -* Implementation does not test the [`[[Prototype]]`][prototype-spec] of - objects. -* [`Symbol`][] properties are not compared. -* [`WeakMap`][] and [`WeakSet`][] comparison does not rely on their values - but only on their instances. -* [`RegExp`][] lastIndex, flags, and source are always compared, even if these - are not enumerable properties. - -The following example does not throw an [`AssertionError`][] because the -primitives are compared using the [`==` operator][]. - -```mjs -import assert from 'node:assert'; -// WARNING: This does not throw an AssertionError! - -assert.deepEqual('+00000000', false); -``` - -```cjs -const assert = require('node:assert'); -// WARNING: This does not throw an AssertionError! - -assert.deepEqual('+00000000', false); -``` - -"Deep" equality means that the enumerable "own" properties of child objects -are evaluated also: - -```mjs -import assert from 'node:assert'; - -const obj1 = { - a: { - b: 1, - }, -}; -const obj2 = { - a: { - b: 2, - }, -}; -const obj3 = { - a: { - b: 1, - }, -}; -const obj4 = { __proto__: obj1 }; - -assert.deepEqual(obj1, obj1); -// OK - -// Values of b are different: -assert.deepEqual(obj1, obj2); -// AssertionError: { a: { b: 1 } } deepEqual { a: { b: 2 } } - -assert.deepEqual(obj1, obj3); -// OK - -// Prototypes are ignored: -assert.deepEqual(obj1, obj4); -// AssertionError: { a: { b: 1 } } deepEqual {} -``` - -```cjs -const assert = require('node:assert'); - -const obj1 = { - a: { - b: 1, - }, -}; -const obj2 = { - a: { - b: 2, - }, -}; -const obj3 = { - a: { - b: 1, - }, -}; -const obj4 = { __proto__: obj1 }; - -assert.deepEqual(obj1, obj1); -// OK - -// Values of b are different: -assert.deepEqual(obj1, obj2); -// AssertionError: { a: { b: 1 } } deepEqual { a: { b: 2 } } - -assert.deepEqual(obj1, obj3); -// OK - -// Prototypes are ignored: -assert.deepEqual(obj1, obj4); -// AssertionError: { a: { b: 1 } } deepEqual {} -``` - -If the values are not equal, an [`AssertionError`][] is thrown with a `message` -property set equal to the value of the `message` parameter. If the `message` -parameter is undefined, a default error message is assigned. If the `message` -parameter is an instance of an [`Error`][] then it will be thrown instead of the -[`AssertionError`][]. - -## `assert.deepStrictEqual(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -Tests for deep equality between the `actual` and `expected` parameters. -"Deep" equality means that the enumerable "own" properties of child objects -are recursively evaluated also by the following rules. - -### Comparison details - -* Primitive values are compared using [`Object.is()`][]. -* [Type tags][Object.prototype.toString()] of objects should be the same. -* [`[[Prototype]]`][prototype-spec] of objects are compared using - the [`===` operator][]. -* Only [enumerable "own" properties][] are considered. -* [`Error`][] names, messages, causes, and errors are always compared, - even if these are not enumerable properties. - `errors` is also compared. -* Enumerable own [`Symbol`][] properties are compared as well. -* [Object wrappers][] are compared both as objects and unwrapped values. -* `Object` properties are compared unordered. -* [`Map`][] keys and [`Set`][] items are compared unordered. -* Recursion stops when both sides differ or both sides encounter a circular - reference. -* [`WeakMap`][] and [`WeakSet`][] comparison does not rely on their values. See - below for further details. -* [`RegExp`][] lastIndex, flags, and source are always compared, even if these - are not enumerable properties. - -```mjs -import assert from 'node:assert/strict'; - -// This fails because 1 !== '1'. -assert.deepStrictEqual({ a: 1 }, { a: '1' }); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// { -// + a: 1 -// - a: '1' -// } - -// The following objects don't have own properties -const date = new Date(); -const object = {}; -const fakeDate = {}; -Object.setPrototypeOf(fakeDate, Date.prototype); - -// Different [[Prototype]]: -assert.deepStrictEqual(object, fakeDate); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + {} -// - Date {} - -// Different type tags: -assert.deepStrictEqual(date, fakeDate); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + 2018-04-26T00:49:08.604Z -// - Date {} - -assert.deepStrictEqual(NaN, NaN); -// OK because Object.is(NaN, NaN) is true. - -// Different unwrapped numbers: -assert.deepStrictEqual(new Number(1), new Number(2)); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + [Number: 1] -// - [Number: 2] - -assert.deepStrictEqual(new String('foo'), Object('foo')); -// OK because the object and the string are identical when unwrapped. - -assert.deepStrictEqual(-0, -0); -// OK - -// Different zeros: -assert.deepStrictEqual(0, -0); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + 0 -// - -0 - -const symbol1 = Symbol(); -const symbol2 = Symbol(); -assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol1]: 1 }); -// OK, because it is the same symbol on both objects. - -assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol2]: 1 }); -// AssertionError [ERR_ASSERTION]: Inputs identical but not reference equal: -// -// { -// [Symbol()]: 1 -// } - -const weakMap1 = new WeakMap(); -const weakMap2 = new WeakMap([[{}, {}]]); -const weakMap3 = new WeakMap(); -weakMap3.unequal = true; - -assert.deepStrictEqual(weakMap1, weakMap2); -// OK, because it is impossible to compare the entries - -// Fails because weakMap3 has a property that weakMap1 does not contain: -assert.deepStrictEqual(weakMap1, weakMap3); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// WeakMap { -// + [items unknown] -// - [items unknown], -// - unequal: true -// } -``` - -```cjs -const assert = require('node:assert/strict'); - -// This fails because 1 !== '1'. -assert.deepStrictEqual({ a: 1 }, { a: '1' }); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// { -// + a: 1 -// - a: '1' -// } - -// The following objects don't have own properties -const date = new Date(); -const object = {}; -const fakeDate = {}; -Object.setPrototypeOf(fakeDate, Date.prototype); - -// Different [[Prototype]]: -assert.deepStrictEqual(object, fakeDate); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + {} -// - Date {} - -// Different type tags: -assert.deepStrictEqual(date, fakeDate); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + 2018-04-26T00:49:08.604Z -// - Date {} - -assert.deepStrictEqual(NaN, NaN); -// OK because Object.is(NaN, NaN) is true. - -// Different unwrapped numbers: -assert.deepStrictEqual(new Number(1), new Number(2)); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + [Number: 1] -// - [Number: 2] - -assert.deepStrictEqual(new String('foo'), Object('foo')); -// OK because the object and the string are identical when unwrapped. - -assert.deepStrictEqual(-0, -0); -// OK - -// Different zeros: -assert.deepStrictEqual(0, -0); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// + 0 -// - -0 - -const symbol1 = Symbol(); -const symbol2 = Symbol(); -assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol1]: 1 }); -// OK, because it is the same symbol on both objects. - -assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol2]: 1 }); -// AssertionError [ERR_ASSERTION]: Inputs identical but not reference equal: -// -// { -// [Symbol()]: 1 -// } - -const weakMap1 = new WeakMap(); -const weakMap2 = new WeakMap([[{}, {}]]); -const weakMap3 = new WeakMap(); -weakMap3.unequal = true; - -assert.deepStrictEqual(weakMap1, weakMap2); -// OK, because it is impossible to compare the entries - -// Fails because weakMap3 has a property that weakMap1 does not contain: -assert.deepStrictEqual(weakMap1, weakMap3); -// AssertionError: Expected inputs to be strictly deep-equal: -// + actual - expected -// -// WeakMap { -// + [items unknown] -// - [items unknown], -// - unequal: true -// } -``` - -If the values are not equal, an [`AssertionError`][] is thrown with a `message` -property set equal to the value of the `message` parameter. If the `message` -parameter is undefined, a default error message is assigned. If the `message` -parameter is an instance of an [`Error`][] then it will be thrown instead of the -`AssertionError`. - -## `assert.doesNotMatch(string, regexp[, message])` - - - -* `string` {string} -* `regexp` {RegExp} -* `message` {string|Error} - -Expects the `string` input not to match the regular expression. - -```mjs -import assert from 'node:assert/strict'; - -assert.doesNotMatch('I will fail', /fail/); -// AssertionError [ERR_ASSERTION]: The input was expected to not match the ... - -assert.doesNotMatch(123, /pass/); -// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. - -assert.doesNotMatch('I will pass', /different/); -// OK -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.doesNotMatch('I will fail', /fail/); -// AssertionError [ERR_ASSERTION]: The input was expected to not match the ... - -assert.doesNotMatch(123, /pass/); -// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. - -assert.doesNotMatch('I will pass', /different/); -// OK -``` - -If the values do match, or if the `string` argument is of another type than -`string`, an [`AssertionError`][] is thrown with a `message` property set equal -to the value of the `message` parameter. If the `message` parameter is -undefined, a default error message is assigned. If the `message` parameter is an -instance of an [`Error`][] then it will be thrown instead of the -[`AssertionError`][]. - -## `assert.doesNotReject(asyncFn[, error][, message])` - - - -* `asyncFn` {Function|Promise} -* `error` {RegExp|Function} -* `message` {string} - -Awaits the `asyncFn` promise or, if `asyncFn` is a function, immediately -calls the function and awaits the returned promise to complete. It will then -check that the promise is not rejected. - -If `asyncFn` is a function and it throws an error synchronously, -`assert.doesNotReject()` will return a rejected `Promise` with that error. If -the function does not return a promise, `assert.doesNotReject()` will return a -rejected `Promise` with an [`ERR_INVALID_RETURN_VALUE`][] error. In both cases -the error handler is skipped. - -Using `assert.doesNotReject()` is actually not useful because there is little -benefit in catching a rejection and then rejecting it again. Instead, consider -adding a comment next to the specific code path that should not reject and keep -error messages as expressive as possible. - -If specified, `error` can be a [`Class`][], [`RegExp`][], or a validation -function. See [`assert.throws()`][] for more details. - -Besides the async nature to await the completion behaves identically to -[`assert.doesNotThrow()`][]. - -```mjs -import assert from 'node:assert/strict'; - -await assert.doesNotReject( - async () => { - throw new TypeError('Wrong value'); - }, - SyntaxError, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -(async () => { - await assert.doesNotReject( - async () => { - throw new TypeError('Wrong value'); - }, - SyntaxError, - ); -})(); -``` - -```mjs -import assert from 'node:assert/strict'; - -assert.doesNotReject(Promise.reject(new TypeError('Wrong value'))) - .then(() => { - // ... - }); -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.doesNotReject(Promise.reject(new TypeError('Wrong value'))) - .then(() => { - // ... - }); -``` - -## `assert.doesNotThrow(fn[, error][, message])` - - - -* `fn` {Function} -* `error` {RegExp|Function} -* `message` {string} - -Asserts that the function `fn` does not throw an error. - -Using `assert.doesNotThrow()` is actually not useful because there -is no benefit in catching an error and then rethrowing it. Instead, consider -adding a comment next to the specific code path that should not throw and keep -error messages as expressive as possible. - -When `assert.doesNotThrow()` is called, it will immediately call the `fn` -function. - -If an error is thrown and it is the same type as that specified by the `error` -parameter, then an [`AssertionError`][] is thrown. If the error is of a -different type, or if the `error` parameter is undefined, the error is -propagated back to the caller. - -If specified, `error` can be a [`Class`][], [`RegExp`][], or a validation -function. See [`assert.throws()`][] for more details. - -The following, for instance, will throw the [`TypeError`][] because there is no -matching error type in the assertion: - -```mjs -import assert from 'node:assert/strict'; - -assert.doesNotThrow( - () => { - throw new TypeError('Wrong value'); - }, - SyntaxError, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.doesNotThrow( - () => { - throw new TypeError('Wrong value'); - }, - SyntaxError, -); -``` - -However, the following will result in an [`AssertionError`][] with the message -'Got unwanted exception...': - -```mjs -import assert from 'node:assert/strict'; - -assert.doesNotThrow( - () => { - throw new TypeError('Wrong value'); - }, - TypeError, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.doesNotThrow( - () => { - throw new TypeError('Wrong value'); - }, - TypeError, -); -``` - -If an [`AssertionError`][] is thrown and a value is provided for the `message` -parameter, the value of `message` will be appended to the [`AssertionError`][] -message: - -```mjs -import assert from 'node:assert/strict'; - -assert.doesNotThrow( - () => { - throw new TypeError('Wrong value'); - }, - /Wrong value/, - 'Whoops', -); -// Throws: AssertionError: Got unwanted exception: Whoops -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.doesNotThrow( - () => { - throw new TypeError('Wrong value'); - }, - /Wrong value/, - 'Whoops', -); -// Throws: AssertionError: Got unwanted exception: Whoops -``` - -## `assert.equal(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -**Strict assertion mode** - -An alias of [`assert.strictEqual()`][]. - -**Legacy assertion mode** - -> Stability: 3 - Legacy: Use [`assert.strictEqual()`][] instead. - -Tests shallow, coercive equality between the `actual` and `expected` parameters -using the [`==` operator][]. `NaN` is specially handled -and treated as being identical if both sides are `NaN`. - -```mjs -import assert from 'node:assert'; - -assert.equal(1, 1); -// OK, 1 == 1 -assert.equal(1, '1'); -// OK, 1 == '1' -assert.equal(NaN, NaN); -// OK - -assert.equal(1, 2); -// AssertionError: 1 == 2 -assert.equal({ a: { b: 1 } }, { a: { b: 1 } }); -// AssertionError: { a: { b: 1 } } == { a: { b: 1 } } -``` - -```cjs -const assert = require('node:assert'); - -assert.equal(1, 1); -// OK, 1 == 1 -assert.equal(1, '1'); -// OK, 1 == '1' -assert.equal(NaN, NaN); -// OK - -assert.equal(1, 2); -// AssertionError: 1 == 2 -assert.equal({ a: { b: 1 } }, { a: { b: 1 } }); -// AssertionError: { a: { b: 1 } } == { a: { b: 1 } } -``` - -If the values are not equal, an [`AssertionError`][] is thrown with a `message` -property set equal to the value of the `message` parameter. If the `message` -parameter is undefined, a default error message is assigned. If the `message` -parameter is an instance of an [`Error`][] then it will be thrown instead of the -`AssertionError`. - -## `assert.fail([message])` - - - -* `message` {string|Error} **Default:** `'Failed'` - -Throws an [`AssertionError`][] with the provided error message or a default -error message. If the `message` parameter is an instance of an [`Error`][] then -it will be thrown instead of the [`AssertionError`][]. - -```mjs -import assert from 'node:assert/strict'; - -assert.fail(); -// AssertionError [ERR_ASSERTION]: Failed - -assert.fail('boom'); -// AssertionError [ERR_ASSERTION]: boom - -assert.fail(new TypeError('need array')); -// TypeError: need array -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.fail(); -// AssertionError [ERR_ASSERTION]: Failed - -assert.fail('boom'); -// AssertionError [ERR_ASSERTION]: boom - -assert.fail(new TypeError('need array')); -// TypeError: need array -``` - -Using `assert.fail()` with more than two arguments is possible but deprecated. -See below for further details. - -## `assert.fail(actual, expected[, message[, operator[, stackStartFn]]])` - - - -> Stability: 0 - Deprecated: Use `assert.fail([message])` or other assert -> functions instead. - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} -* `operator` {string} **Default:** `'!='` -* `stackStartFn` {Function} **Default:** `assert.fail` - -If `message` is falsy, the error message is set as the values of `actual` and -`expected` separated by the provided `operator`. If just the two `actual` and -`expected` arguments are provided, `operator` will default to `'!='`. If -`message` is provided as third argument it will be used as the error message and -the other arguments will be stored as properties on the thrown object. If -`stackStartFn` is provided, all stack frames above that function will be -removed from stacktrace (see [`Error.captureStackTrace`][]). If no arguments are -given, the default message `Failed` will be used. - -```mjs -import assert from 'node:assert/strict'; - -assert.fail('a', 'b'); -// AssertionError [ERR_ASSERTION]: 'a' != 'b' - -assert.fail(1, 2, undefined, '>'); -// AssertionError [ERR_ASSERTION]: 1 > 2 - -assert.fail(1, 2, 'fail'); -// AssertionError [ERR_ASSERTION]: fail - -assert.fail(1, 2, 'whoops', '>'); -// AssertionError [ERR_ASSERTION]: whoops - -assert.fail(1, 2, new TypeError('need array')); -// TypeError: need array -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.fail('a', 'b'); -// AssertionError [ERR_ASSERTION]: 'a' != 'b' - -assert.fail(1, 2, undefined, '>'); -// AssertionError [ERR_ASSERTION]: 1 > 2 - -assert.fail(1, 2, 'fail'); -// AssertionError [ERR_ASSERTION]: fail - -assert.fail(1, 2, 'whoops', '>'); -// AssertionError [ERR_ASSERTION]: whoops - -assert.fail(1, 2, new TypeError('need array')); -// TypeError: need array -``` - -In the last three cases `actual`, `expected`, and `operator` have no -influence on the error message. - -Example use of `stackStartFn` for truncating the exception's stacktrace: - -```mjs -import assert from 'node:assert/strict'; - -function suppressFrame() { - assert.fail('a', 'b', undefined, '!==', suppressFrame); -} -suppressFrame(); -// AssertionError [ERR_ASSERTION]: 'a' !== 'b' -// at repl:1:1 -// at ContextifyScript.Script.runInThisContext (vm.js:44:33) -// ... -``` - -```cjs -const assert = require('node:assert/strict'); - -function suppressFrame() { - assert.fail('a', 'b', undefined, '!==', suppressFrame); -} -suppressFrame(); -// AssertionError [ERR_ASSERTION]: 'a' !== 'b' -// at repl:1:1 -// at ContextifyScript.Script.runInThisContext (vm.js:44:33) -// ... -``` - -## `assert.ifError(value)` - - - -* `value` {any} - -Throws `value` if `value` is not `undefined` or `null`. This is useful when -testing the `error` argument in callbacks. The stack trace contains all frames -from the error passed to `ifError()` including the potential new frames for -`ifError()` itself. - -```mjs -import assert from 'node:assert/strict'; - -assert.ifError(null); -// OK -assert.ifError(0); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 0 -assert.ifError('error'); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'error' -assert.ifError(new Error()); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Error - -// Create some random error frames. -let err; -(function errorFrame() { - err = new Error('test error'); -})(); - -(function ifErrorFrame() { - assert.ifError(err); -})(); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error -// at ifErrorFrame -// at errorFrame -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.ifError(null); -// OK -assert.ifError(0); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 0 -assert.ifError('error'); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'error' -assert.ifError(new Error()); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Error - -// Create some random error frames. -let err; -(function errorFrame() { - err = new Error('test error'); -})(); - -(function ifErrorFrame() { - assert.ifError(err); -})(); -// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error -// at ifErrorFrame -// at errorFrame -``` - -## `assert.match(string, regexp[, message])` - - - -* `string` {string} -* `regexp` {RegExp} -* `message` {string|Error} - -Expects the `string` input to match the regular expression. - -```mjs -import assert from 'node:assert/strict'; - -assert.match('I will fail', /pass/); -// AssertionError [ERR_ASSERTION]: The input did not match the regular ... - -assert.match(123, /pass/); -// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. - -assert.match('I will pass', /pass/); -// OK -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.match('I will fail', /pass/); -// AssertionError [ERR_ASSERTION]: The input did not match the regular ... - -assert.match(123, /pass/); -// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. - -assert.match('I will pass', /pass/); -// OK -``` - -If the values do not match, or if the `string` argument is of another type than -`string`, an [`AssertionError`][] is thrown with a `message` property set equal -to the value of the `message` parameter. If the `message` parameter is -undefined, a default error message is assigned. If the `message` parameter is an -instance of an [`Error`][] then it will be thrown instead of the -[`AssertionError`][]. - -## `assert.notDeepEqual(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -**Strict assertion mode** - -An alias of [`assert.notDeepStrictEqual()`][]. - -**Legacy assertion mode** - -> Stability: 3 - Legacy: Use [`assert.notDeepStrictEqual()`][] instead. - -Tests for any deep inequality. Opposite of [`assert.deepEqual()`][]. - -```mjs -import assert from 'node:assert'; - -const obj1 = { - a: { - b: 1, - }, -}; -const obj2 = { - a: { - b: 2, - }, -}; -const obj3 = { - a: { - b: 1, - }, -}; -const obj4 = { __proto__: obj1 }; - -assert.notDeepEqual(obj1, obj1); -// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } - -assert.notDeepEqual(obj1, obj2); -// OK - -assert.notDeepEqual(obj1, obj3); -// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } - -assert.notDeepEqual(obj1, obj4); -// OK -``` - -```cjs -const assert = require('node:assert'); - -const obj1 = { - a: { - b: 1, - }, -}; -const obj2 = { - a: { - b: 2, - }, -}; -const obj3 = { - a: { - b: 1, - }, -}; -const obj4 = { __proto__: obj1 }; - -assert.notDeepEqual(obj1, obj1); -// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } - -assert.notDeepEqual(obj1, obj2); -// OK - -assert.notDeepEqual(obj1, obj3); -// AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } } - -assert.notDeepEqual(obj1, obj4); -// OK -``` - -If the values are deeply equal, an [`AssertionError`][] is thrown with a -`message` property set equal to the value of the `message` parameter. If the -`message` parameter is undefined, a default error message is assigned. If the -`message` parameter is an instance of an [`Error`][] then it will be thrown -instead of the `AssertionError`. - -## `assert.notDeepStrictEqual(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -Tests for deep strict inequality. Opposite of [`assert.deepStrictEqual()`][]. - -```mjs -import assert from 'node:assert/strict'; - -assert.notDeepStrictEqual({ a: 1 }, { a: '1' }); -// OK -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.notDeepStrictEqual({ a: 1 }, { a: '1' }); -// OK -``` - -If the values are deeply and strictly equal, an [`AssertionError`][] is thrown -with a `message` property set equal to the value of the `message` parameter. If -the `message` parameter is undefined, a default error message is assigned. If -the `message` parameter is an instance of an [`Error`][] then it will be thrown -instead of the [`AssertionError`][]. - -## `assert.notEqual(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -**Strict assertion mode** - -An alias of [`assert.notStrictEqual()`][]. - -**Legacy assertion mode** - -> Stability: 3 - Legacy: Use [`assert.notStrictEqual()`][] instead. - -Tests shallow, coercive inequality with the [`!=` operator][]. `NaN` is -specially handled and treated as being identical if both sides are `NaN`. - -```mjs -import assert from 'node:assert'; - -assert.notEqual(1, 2); -// OK - -assert.notEqual(1, 1); -// AssertionError: 1 != 1 - -assert.notEqual(1, '1'); -// AssertionError: 1 != '1' -``` - -```cjs -const assert = require('node:assert'); - -assert.notEqual(1, 2); -// OK - -assert.notEqual(1, 1); -// AssertionError: 1 != 1 - -assert.notEqual(1, '1'); -// AssertionError: 1 != '1' -``` - -If the values are equal, an [`AssertionError`][] is thrown with a `message` -property set equal to the value of the `message` parameter. If the `message` -parameter is undefined, a default error message is assigned. If the `message` -parameter is an instance of an [`Error`][] then it will be thrown instead of the -`AssertionError`. - -## `assert.notStrictEqual(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -Tests strict inequality between the `actual` and `expected` parameters as -determined by [`Object.is()`][]. - -```mjs -import assert from 'node:assert/strict'; - -assert.notStrictEqual(1, 2); -// OK - -assert.notStrictEqual(1, 1); -// AssertionError [ERR_ASSERTION]: Expected "actual" to be strictly unequal to: -// -// 1 - -assert.notStrictEqual(1, '1'); -// OK -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.notStrictEqual(1, 2); -// OK - -assert.notStrictEqual(1, 1); -// AssertionError [ERR_ASSERTION]: Expected "actual" to be strictly unequal to: -// -// 1 - -assert.notStrictEqual(1, '1'); -// OK -``` - -If the values are strictly equal, an [`AssertionError`][] is thrown with a -`message` property set equal to the value of the `message` parameter. If the -`message` parameter is undefined, a default error message is assigned. If the -`message` parameter is an instance of an [`Error`][] then it will be thrown -instead of the `AssertionError`. - -## `assert.ok(value[, message])` - - - -* `value` {any} -* `message` {string|Error} - -Tests if `value` is truthy. It is equivalent to -`assert.equal(!!value, true, message)`. - -If `value` is not truthy, an [`AssertionError`][] is thrown with a `message` -property set equal to the value of the `message` parameter. If the `message` -parameter is `undefined`, a default error message is assigned. If the `message` -parameter is an instance of an [`Error`][] then it will be thrown instead of the -`AssertionError`. -If no arguments are passed in at all `message` will be set to the string: -``'No value argument passed to `assert.ok()`'``. - -Be aware that in the `repl` the error message will be different to the one -thrown in a file! See below for further details. - -```mjs -import assert from 'node:assert/strict'; - -assert.ok(true); -// OK -assert.ok(1); -// OK - -assert.ok(); -// AssertionError: No value argument passed to `assert.ok()` - -assert.ok(false, 'it\'s false'); -// AssertionError: it's false - -// In the repl: -assert.ok(typeof 123 === 'string'); -// AssertionError: false == true - -// In a file (e.g. test.js): -assert.ok(typeof 123 === 'string'); -// AssertionError: The expression evaluated to a falsy value: -// -// assert.ok(typeof 123 === 'string') - -assert.ok(false); -// AssertionError: The expression evaluated to a falsy value: -// -// assert.ok(false) - -assert.ok(0); -// AssertionError: The expression evaluated to a falsy value: -// -// assert.ok(0) -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.ok(true); -// OK -assert.ok(1); -// OK - -assert.ok(); -// AssertionError: No value argument passed to `assert.ok()` - -assert.ok(false, 'it\'s false'); -// AssertionError: it's false - -// In the repl: -assert.ok(typeof 123 === 'string'); -// AssertionError: false == true - -// In a file (e.g. test.js): -assert.ok(typeof 123 === 'string'); -// AssertionError: The expression evaluated to a falsy value: -// -// assert.ok(typeof 123 === 'string') - -assert.ok(false); -// AssertionError: The expression evaluated to a falsy value: -// -// assert.ok(false) - -assert.ok(0); -// AssertionError: The expression evaluated to a falsy value: -// -// assert.ok(0) -``` - -```mjs -import assert from 'node:assert/strict'; - -// Using `assert()` works the same: -assert(0); -// AssertionError: The expression evaluated to a falsy value: -// -// assert(0) -``` - -```cjs -const assert = require('node:assert'); - -// Using `assert()` works the same: -assert(0); -// AssertionError: The expression evaluated to a falsy value: -// -// assert(0) -``` - -## `assert.rejects(asyncFn[, error][, message])` - - - -* `asyncFn` {Function|Promise} -* `error` {RegExp|Function|Object|Error} -* `message` {string} - -Awaits the `asyncFn` promise or, if `asyncFn` is a function, immediately -calls the function and awaits the returned promise to complete. It will then -check that the promise is rejected. - -If `asyncFn` is a function and it throws an error synchronously, -`assert.rejects()` will return a rejected `Promise` with that error. If the -function does not return a promise, `assert.rejects()` will return a rejected -`Promise` with an [`ERR_INVALID_RETURN_VALUE`][] error. In both cases the error -handler is skipped. - -Besides the async nature to await the completion behaves identically to -[`assert.throws()`][]. - -If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function, -an object where each property will be tested for, or an instance of error where -each property will be tested for including the non-enumerable `message` and -`name` properties. - -If specified, `message` will be the message provided by the [`AssertionError`][] -if the `asyncFn` fails to reject. - -```mjs -import assert from 'node:assert/strict'; - -await assert.rejects( - async () => { - throw new TypeError('Wrong value'); - }, - { - name: 'TypeError', - message: 'Wrong value', - }, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -(async () => { - await assert.rejects( - async () => { - throw new TypeError('Wrong value'); - }, - { - name: 'TypeError', - message: 'Wrong value', - }, - ); -})(); -``` - -```mjs -import assert from 'node:assert/strict'; - -await assert.rejects( - async () => { - throw new TypeError('Wrong value'); - }, - (err) => { - assert.strictEqual(err.name, 'TypeError'); - assert.strictEqual(err.message, 'Wrong value'); - return true; - }, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -(async () => { - await assert.rejects( - async () => { - throw new TypeError('Wrong value'); - }, - (err) => { - assert.strictEqual(err.name, 'TypeError'); - assert.strictEqual(err.message, 'Wrong value'); - return true; - }, - ); -})(); -``` - -```mjs -import assert from 'node:assert/strict'; - -assert.rejects( - Promise.reject(new Error('Wrong value')), - Error, -).then(() => { - // ... -}); -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.rejects( - Promise.reject(new Error('Wrong value')), - Error, -).then(() => { - // ... -}); -``` - -`error` cannot be a string. If a string is provided as the second -argument, then `error` is assumed to be omitted and the string will be used for -`message` instead. This can lead to easy-to-miss mistakes. Please read the -example in [`assert.throws()`][] carefully if using a string as the second -argument gets considered. - -## `assert.strictEqual(actual, expected[, message])` - - - -* `actual` {any} -* `expected` {any} -* `message` {string|Error} - -Tests strict equality between the `actual` and `expected` parameters as -determined by [`Object.is()`][]. - -```mjs -import assert from 'node:assert/strict'; - -assert.strictEqual(1, 2); -// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: -// -// 1 !== 2 - -assert.strictEqual(1, 1); -// OK - -assert.strictEqual('Hello foobar', 'Hello World!'); -// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: -// + actual - expected -// -// + 'Hello foobar' -// - 'Hello World!' -// ^ - -const apples = 1; -const oranges = 2; -assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`); -// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2 - -assert.strictEqual(1, '1', new TypeError('Inputs are not identical')); -// TypeError: Inputs are not identical -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.strictEqual(1, 2); -// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: -// -// 1 !== 2 - -assert.strictEqual(1, 1); -// OK - -assert.strictEqual('Hello foobar', 'Hello World!'); -// AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: -// + actual - expected -// -// + 'Hello foobar' -// - 'Hello World!' -// ^ - -const apples = 1; -const oranges = 2; -assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`); -// AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2 - -assert.strictEqual(1, '1', new TypeError('Inputs are not identical')); -// TypeError: Inputs are not identical -``` - -If the values are not strictly equal, an [`AssertionError`][] is thrown with a -`message` property set equal to the value of the `message` parameter. If the -`message` parameter is undefined, a default error message is assigned. If the -`message` parameter is an instance of an [`Error`][] then it will be thrown -instead of the [`AssertionError`][]. - -## `assert.throws(fn[, error][, message])` - - - -* `fn` {Function} -* `error` {RegExp|Function|Object|Error} -* `message` {string} - -Expects the function `fn` to throw an error. - -If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function, -a validation object where each property will be tested for strict deep equality, -or an instance of error where each property will be tested for strict deep -equality including the non-enumerable `message` and `name` properties. When -using an object, it is also possible to use a regular expression, when -validating against a string property. See below for examples. - -If specified, `message` will be appended to the message provided by the -`AssertionError` if the `fn` call fails to throw or in case the error validation -fails. - -Custom validation object/error instance: - -```mjs -import assert from 'node:assert/strict'; - -const err = new TypeError('Wrong value'); -err.code = 404; -err.foo = 'bar'; -err.info = { - nested: true, - baz: 'text', -}; -err.reg = /abc/i; - -assert.throws( - () => { - throw err; - }, - { - name: 'TypeError', - message: 'Wrong value', - info: { - nested: true, - baz: 'text', - }, - // Only properties on the validation object will be tested for. - // Using nested objects requires all properties to be present. Otherwise - // the validation is going to fail. - }, -); - -// Using regular expressions to validate error properties: -assert.throws( - () => { - throw err; - }, - { - // The `name` and `message` properties are strings and using regular - // expressions on those will match against the string. If they fail, an - // error is thrown. - name: /^TypeError$/, - message: /Wrong/, - foo: 'bar', - info: { - nested: true, - // It is not possible to use regular expressions for nested properties! - baz: 'text', - }, - // The `reg` property contains a regular expression and only if the - // validation object contains an identical regular expression, it is going - // to pass. - reg: /abc/i, - }, -); - -// Fails due to the different `message` and `name` properties: -assert.throws( - () => { - const otherErr = new Error('Not found'); - // Copy all enumerable properties from `err` to `otherErr`. - for (const [key, value] of Object.entries(err)) { - otherErr[key] = value; - } - throw otherErr; - }, - // The error's `message` and `name` properties will also be checked when using - // an error as validation object. - err, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -const err = new TypeError('Wrong value'); -err.code = 404; -err.foo = 'bar'; -err.info = { - nested: true, - baz: 'text', -}; -err.reg = /abc/i; - -assert.throws( - () => { - throw err; - }, - { - name: 'TypeError', - message: 'Wrong value', - info: { - nested: true, - baz: 'text', - }, - // Only properties on the validation object will be tested for. - // Using nested objects requires all properties to be present. Otherwise - // the validation is going to fail. - }, -); - -// Using regular expressions to validate error properties: -assert.throws( - () => { - throw err; - }, - { - // The `name` and `message` properties are strings and using regular - // expressions on those will match against the string. If they fail, an - // error is thrown. - name: /^TypeError$/, - message: /Wrong/, - foo: 'bar', - info: { - nested: true, - // It is not possible to use regular expressions for nested properties! - baz: 'text', - }, - // The `reg` property contains a regular expression and only if the - // validation object contains an identical regular expression, it is going - // to pass. - reg: /abc/i, - }, -); - -// Fails due to the different `message` and `name` properties: -assert.throws( - () => { - const otherErr = new Error('Not found'); - // Copy all enumerable properties from `err` to `otherErr`. - for (const [key, value] of Object.entries(err)) { - otherErr[key] = value; - } - throw otherErr; - }, - // The error's `message` and `name` properties will also be checked when using - // an error as validation object. - err, -); -``` - -Validate instanceof using constructor: - -```mjs -import assert from 'node:assert/strict'; - -assert.throws( - () => { - throw new Error('Wrong value'); - }, - Error, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.throws( - () => { - throw new Error('Wrong value'); - }, - Error, -); -``` - -Validate error message using [`RegExp`][]: - -Using a regular expression runs `.toString` on the error object, and will -therefore also include the error name. - -```mjs -import assert from 'node:assert/strict'; - -assert.throws( - () => { - throw new Error('Wrong value'); - }, - /^Error: Wrong value$/, -); -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.throws( - () => { - throw new Error('Wrong value'); - }, - /^Error: Wrong value$/, -); -``` - -Custom error validation: - -The function must return `true` to indicate all internal validations passed. -It will otherwise fail with an [`AssertionError`][]. - -```mjs -import assert from 'node:assert/strict'; - -assert.throws( - () => { - throw new Error('Wrong value'); - }, - (err) => { - assert(err instanceof Error); - assert(/value/.test(err)); - // Avoid returning anything from validation functions besides `true`. - // Otherwise, it's not clear what part of the validation failed. Instead, - // throw an error about the specific validation that failed (as done in this - // example) and add as much helpful debugging information to that error as - // possible. - return true; - }, - 'unexpected error', -); -``` - -```cjs -const assert = require('node:assert/strict'); - -assert.throws( - () => { - throw new Error('Wrong value'); - }, - (err) => { - assert(err instanceof Error); - assert(/value/.test(err)); - // Avoid returning anything from validation functions besides `true`. - // Otherwise, it's not clear what part of the validation failed. Instead, - // throw an error about the specific validation that failed (as done in this - // example) and add as much helpful debugging information to that error as - // possible. - return true; - }, - 'unexpected error', -); -``` - -`error` cannot be a string. If a string is provided as the second -argument, then `error` is assumed to be omitted and the string will be used for -`message` instead. This can lead to easy-to-miss mistakes. Using the same -message as the thrown error message is going to result in an -`ERR_AMBIGUOUS_ARGUMENT` error. Please read the example below carefully if using -a string as the second argument gets considered: - -```mjs -import assert from 'node:assert/strict'; - -function throwingFirst() { - throw new Error('First'); -} - -function throwingSecond() { - throw new Error('Second'); -} - -function notThrowing() {} - -// The second argument is a string and the input function threw an Error. -// The first case will not throw as it does not match for the error message -// thrown by the input function! -assert.throws(throwingFirst, 'Second'); -// In the next example the message has no benefit over the message from the -// error and since it is not clear if the user intended to actually match -// against the error message, Node.js throws an `ERR_AMBIGUOUS_ARGUMENT` error. -assert.throws(throwingSecond, 'Second'); -// TypeError [ERR_AMBIGUOUS_ARGUMENT] - -// The string is only used (as message) in case the function does not throw: -assert.throws(notThrowing, 'Second'); -// AssertionError [ERR_ASSERTION]: Missing expected exception: Second - -// If it was intended to match for the error message do this instead: -// It does not throw because the error messages match. -assert.throws(throwingSecond, /Second$/); - -// If the error message does not match, an AssertionError is thrown. -assert.throws(throwingFirst, /Second$/); -// AssertionError [ERR_ASSERTION] -``` - -```cjs -const assert = require('node:assert/strict'); - -function throwingFirst() { - throw new Error('First'); -} - -function throwingSecond() { - throw new Error('Second'); -} - -function notThrowing() {} - -// The second argument is a string and the input function threw an Error. -// The first case will not throw as it does not match for the error message -// thrown by the input function! -assert.throws(throwingFirst, 'Second'); -// In the next example the message has no benefit over the message from the -// error and since it is not clear if the user intended to actually match -// against the error message, Node.js throws an `ERR_AMBIGUOUS_ARGUMENT` error. -assert.throws(throwingSecond, 'Second'); -// TypeError [ERR_AMBIGUOUS_ARGUMENT] - -// The string is only used (as message) in case the function does not throw: -assert.throws(notThrowing, 'Second'); -// AssertionError [ERR_ASSERTION]: Missing expected exception: Second - -// If it was intended to match for the error message do this instead: -// It does not throw because the error messages match. -assert.throws(throwingSecond, /Second$/); - -// If the error message does not match, an AssertionError is thrown. -assert.throws(throwingFirst, /Second$/); -// AssertionError [ERR_ASSERTION] -``` - -Due to the confusing error-prone notation, avoid a string as the second -argument. - -[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript -[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring -[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality -[`===` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality -[`==` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality -[`AssertionError`]: #class-assertassertionerror -[`CallTracker`]: #class-assertcalltracker -[`Class`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes -[`ERR_INVALID_RETURN_VALUE`]: errors.md#err_invalid_return_value -[`Error.captureStackTrace`]: errors.md#errorcapturestacktracetargetobject-constructoropt -[`Error`]: errors.md#class-error -[`Map`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map -[`Object.is()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is -[`RegExp`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions -[`Set`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set -[`Symbol`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol -[`TypeError`]: errors.md#class-typeerror -[`WeakMap`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap -[`WeakSet`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet -[`assert.deepEqual()`]: #assertdeepequalactual-expected-message -[`assert.deepStrictEqual()`]: #assertdeepstrictequalactual-expected-message -[`assert.doesNotThrow()`]: #assertdoesnotthrowfn-error-message -[`assert.equal()`]: #assertequalactual-expected-message -[`assert.notDeepEqual()`]: #assertnotdeepequalactual-expected-message -[`assert.notDeepStrictEqual()`]: #assertnotdeepstrictequalactual-expected-message -[`assert.notEqual()`]: #assertnotequalactual-expected-message -[`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message -[`assert.ok()`]: #assertokvalue-message -[`assert.strictEqual()`]: #assertstrictequalactual-expected-message -[`assert.throws()`]: #assertthrowsfn-error-message -[`getColorDepth()`]: tty.md#writestreamgetcolordepthenv -[`mock`]: test.md#mocking -[`process.on('exit')`]: process.md#event-exit -[`tracker.calls()`]: #trackercallsfn-exact -[`tracker.verify()`]: #trackerverify -[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties -[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots diff --git a/fixtures/async_context.md b/fixtures/async_context.md deleted file mode 100644 index d1be2fb..0000000 --- a/fixtures/async_context.md +++ /dev/null @@ -1,888 +0,0 @@ -# Asynchronous context tracking - - - -> Stability: 2 - Stable - - - -## Introduction - -These classes are used to associate state and propagate it throughout -callbacks and promise chains. -They allow storing data throughout the lifetime of a web request -or any other asynchronous duration. It is similar to thread-local storage -in other languages. - -The `AsyncLocalStorage` and `AsyncResource` classes are part of the -`node:async_hooks` module: - -```mjs -import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'; -``` - -```cjs -const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks'); -``` - -## Class: `AsyncLocalStorage` - - - -This class creates stores that stay coherent through asynchronous operations. - -While you can create your own implementation on top of the `node:async_hooks` -module, `AsyncLocalStorage` should be preferred as it is a performant and memory -safe implementation that involves significant optimizations that are non-obvious -to implement. - -The following example uses `AsyncLocalStorage` to build a simple logger -that assigns IDs to incoming HTTP requests and includes them in messages -logged within each request. - -```mjs -import http from 'node:http'; -import { AsyncLocalStorage } from 'node:async_hooks'; - -const asyncLocalStorage = new AsyncLocalStorage(); - -function logWithId(msg) { - const id = asyncLocalStorage.getStore(); - console.log(`${id !== undefined ? id : '-'}:`, msg); -} - -let idSeq = 0; -http.createServer((req, res) => { - asyncLocalStorage.run(idSeq++, () => { - logWithId('start'); - // Imagine any chain of async operations here - setImmediate(() => { - logWithId('finish'); - res.end(); - }); - }); -}).listen(8080); - -http.get('http://localhost:8080'); -http.get('http://localhost:8080'); -// Prints: -// 0: start -// 1: start -// 0: finish -// 1: finish -``` - -```cjs -const http = require('node:http'); -const { AsyncLocalStorage } = require('node:async_hooks'); - -const asyncLocalStorage = new AsyncLocalStorage(); - -function logWithId(msg) { - const id = asyncLocalStorage.getStore(); - console.log(`${id !== undefined ? id : '-'}:`, msg); -} - -let idSeq = 0; -http.createServer((req, res) => { - asyncLocalStorage.run(idSeq++, () => { - logWithId('start'); - // Imagine any chain of async operations here - setImmediate(() => { - logWithId('finish'); - res.end(); - }); - }); -}).listen(8080); - -http.get('http://localhost:8080'); -http.get('http://localhost:8080'); -// Prints: -// 0: start -// 1: start -// 0: finish -// 1: finish -``` - -Each instance of `AsyncLocalStorage` maintains an independent storage context. -Multiple instances can safely exist simultaneously without risk of interfering -with each other's data. - -### `new AsyncLocalStorage()` - - - -Creates a new instance of `AsyncLocalStorage`. Store is only provided within a -`run()` call or after an `enterWith()` call. - -### Static method: `AsyncLocalStorage.bind(fn)` - - - -> Stability: 1 - Experimental - -* `fn` {Function} The function to bind to the current execution context. -* Returns: {Function} A new function that calls `fn` within the captured - execution context. - -Binds the given function to the current execution context. - -### Static method: `AsyncLocalStorage.snapshot()` - - - -> Stability: 1 - Experimental - -* Returns: {Function} A new function with the signature - `(fn: (...args) : R, ...args) : R`. - -Captures the current execution context and returns a function that accepts a -function as an argument. Whenever the returned function is called, it -calls the function passed to it within the captured context. - -```js -const asyncLocalStorage = new AsyncLocalStorage(); -const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot()); -const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore())); -console.log(result); // returns 123 -``` - -AsyncLocalStorage.snapshot() can replace the use of AsyncResource for simple -async context tracking purposes, for example: - -```js -class Foo { - #runInAsyncScope = AsyncLocalStorage.snapshot(); - - get() { return this.#runInAsyncScope(() => asyncLocalStorage.getStore()); } -} - -const foo = asyncLocalStorage.run(123, () => new Foo()); -console.log(asyncLocalStorage.run(321, () => foo.get())); // returns 123 -``` - -### `asyncLocalStorage.disable()` - - - -> Stability: 1 - Experimental - -Disables the instance of `AsyncLocalStorage`. All subsequent calls -to `asyncLocalStorage.getStore()` will return `undefined` until -`asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()` is called again. - -When calling `asyncLocalStorage.disable()`, all current contexts linked to the -instance will be exited. - -Calling `asyncLocalStorage.disable()` is required before the -`asyncLocalStorage` can be garbage collected. This does not apply to stores -provided by the `asyncLocalStorage`, as those objects are garbage collected -along with the corresponding async resources. - -Use this method when the `asyncLocalStorage` is not in use anymore -in the current process. - -### `asyncLocalStorage.getStore()` - - - -* Returns: {any} - -Returns the current store. -If called outside of an asynchronous context initialized by -calling `asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()`, it -returns `undefined`. - -### `asyncLocalStorage.enterWith(store)` - - - -> Stability: 1 - Experimental - -* `store` {any} - -Transitions into the context for the remainder of the current -synchronous execution and then persists the store through any following -asynchronous calls. - -Example: - -```js -const store = { id: 1 }; -// Replaces previous store with the given store object -asyncLocalStorage.enterWith(store); -asyncLocalStorage.getStore(); // Returns the store object -someAsyncOperation(() => { - asyncLocalStorage.getStore(); // Returns the same object -}); -``` - -This transition will continue for the _entire_ synchronous execution. -This means that if, for example, the context is entered within an event -handler subsequent event handlers will also run within that context unless -specifically bound to another context with an `AsyncResource`. That is why -`run()` should be preferred over `enterWith()` unless there are strong reasons -to use the latter method. - -```js -const store = { id: 1 }; - -emitter.on('my-event', () => { - asyncLocalStorage.enterWith(store); -}); -emitter.on('my-event', () => { - asyncLocalStorage.getStore(); // Returns the same object -}); - -asyncLocalStorage.getStore(); // Returns undefined -emitter.emit('my-event'); -asyncLocalStorage.getStore(); // Returns the same object -``` - -### `asyncLocalStorage.run(store, callback[, ...args])` - - - -* `store` {any} -* `callback` {Function} -* `...args` {any} - -Runs a function synchronously within a context and returns its -return value. The store is not accessible outside of the callback function. -The store is accessible to any asynchronous operations created within the -callback. - -The optional `args` are passed to the callback function. - -If the callback function throws an error, the error is thrown by `run()` too. -The stacktrace is not impacted by this call and the context is exited. - -Example: - -```js -const store = { id: 2 }; -try { - asyncLocalStorage.run(store, () => { - asyncLocalStorage.getStore(); // Returns the store object - setTimeout(() => { - asyncLocalStorage.getStore(); // Returns the store object - }, 200); - throw new Error(); - }); -} catch (e) { - asyncLocalStorage.getStore(); // Returns undefined - // The error will be caught here -} -``` - -### `asyncLocalStorage.exit(callback[, ...args])` - - - -> Stability: 1 - Experimental - -* `callback` {Function} -* `...args` {any} - -Runs a function synchronously outside of a context and returns its -return value. The store is not accessible within the callback function or -the asynchronous operations created within the callback. Any `getStore()` -call done within the callback function will always return `undefined`. - -The optional `args` are passed to the callback function. - -If the callback function throws an error, the error is thrown by `exit()` too. -The stacktrace is not impacted by this call and the context is re-entered. - -Example: - -```js -// Within a call to run -try { - asyncLocalStorage.getStore(); // Returns the store object or value - asyncLocalStorage.exit(() => { - asyncLocalStorage.getStore(); // Returns undefined - throw new Error(); - }); -} catch (e) { - asyncLocalStorage.getStore(); // Returns the same object or value - // The error will be caught here -} -``` - -### Usage with `async/await` - -If, within an async function, only one `await` call is to run within a context, -the following pattern should be used: - -```js -async function fn() { - await asyncLocalStorage.run(new Map(), () => { - asyncLocalStorage.getStore().set('key', value); - return foo(); // The return value of foo will be awaited - }); -} -``` - -In this example, the store is only available in the callback function and the -functions called by `foo`. Outside of `run`, calling `getStore` will return -`undefined`. - -### Troubleshooting: Context loss - -In most cases, `AsyncLocalStorage` works without issues. In rare situations, the -current store is lost in one of the asynchronous operations. - -If your code is callback-based, it is enough to promisify it with -[`util.promisify()`][] so it starts working with native promises. - -If you need to use a callback-based API or your code assumes -a custom thenable implementation, use the [`AsyncResource`][] class -to associate the asynchronous operation with the correct execution context. -Find the function call responsible for the context loss by logging the content -of `asyncLocalStorage.getStore()` after the calls you suspect are responsible -for the loss. When the code logs `undefined`, the last callback called is -probably responsible for the context loss. - -## Class: `AsyncResource` - - - -The class `AsyncResource` is designed to be extended by the embedder's async -resources. Using this, users can easily trigger the lifetime events of their -own resources. - -The `init` hook will trigger when an `AsyncResource` is instantiated. - -The following is an overview of the `AsyncResource` API. - -```mjs -import { AsyncResource, executionAsyncId } from 'node:async_hooks'; - -// AsyncResource() is meant to be extended. Instantiating a -// new AsyncResource() also triggers init. If triggerAsyncId is omitted then -// async_hook.executionAsyncId() is used. -const asyncResource = new AsyncResource( - type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }, -); - -// Run a function in the execution context of the resource. This will -// * establish the context of the resource -// * trigger the AsyncHooks before callbacks -// * call the provided function `fn` with the supplied arguments -// * trigger the AsyncHooks after callbacks -// * restore the original execution context -asyncResource.runInAsyncScope(fn, thisArg, ...args); - -// Call AsyncHooks destroy callbacks. -asyncResource.emitDestroy(); - -// Return the unique ID assigned to the AsyncResource instance. -asyncResource.asyncId(); - -// Return the trigger ID for the AsyncResource instance. -asyncResource.triggerAsyncId(); -``` - -```cjs -const { AsyncResource, executionAsyncId } = require('node:async_hooks'); - -// AsyncResource() is meant to be extended. Instantiating a -// new AsyncResource() also triggers init. If triggerAsyncId is omitted then -// async_hook.executionAsyncId() is used. -const asyncResource = new AsyncResource( - type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }, -); - -// Run a function in the execution context of the resource. This will -// * establish the context of the resource -// * trigger the AsyncHooks before callbacks -// * call the provided function `fn` with the supplied arguments -// * trigger the AsyncHooks after callbacks -// * restore the original execution context -asyncResource.runInAsyncScope(fn, thisArg, ...args); - -// Call AsyncHooks destroy callbacks. -asyncResource.emitDestroy(); - -// Return the unique ID assigned to the AsyncResource instance. -asyncResource.asyncId(); - -// Return the trigger ID for the AsyncResource instance. -asyncResource.triggerAsyncId(); -``` - -### `new AsyncResource(type[, options])` - -* `type` {string} The type of async event. -* `options` {Object} - * `triggerAsyncId` {number} The ID of the execution context that created this - async event. **Default:** `executionAsyncId()`. - * `requireManualDestroy` {boolean} If set to `true`, disables `emitDestroy` - when the object is garbage collected. This usually does not need to be set - (even if `emitDestroy` is called manually), unless the resource's `asyncId` - is retrieved and the sensitive API's `emitDestroy` is called with it. - When set to `false`, the `emitDestroy` call on garbage collection - will only take place if there is at least one active `destroy` hook. - **Default:** `false`. - -Example usage: - -```js -class DBQuery extends AsyncResource { - constructor(db) { - super('DBQuery'); - this.db = db; - } - - getInfo(query, callback) { - this.db.get(query, (err, data) => { - this.runInAsyncScope(callback, null, err, data); - }); - } - - close() { - this.db = null; - this.emitDestroy(); - } -} -``` - -### Static method: `AsyncResource.bind(fn[, type[, thisArg]])` - - - -* `fn` {Function} The function to bind to the current execution context. -* `type` {string} An optional name to associate with the underlying - `AsyncResource`. -* `thisArg` {any} - -Binds the given function to the current execution context. - -### `asyncResource.bind(fn[, thisArg])` - - - -* `fn` {Function} The function to bind to the current `AsyncResource`. -* `thisArg` {any} - -Binds the given function to execute to this `AsyncResource`'s scope. - -### `asyncResource.runInAsyncScope(fn[, thisArg, ...args])` - - - -* `fn` {Function} The function to call in the execution context of this async - resource. -* `thisArg` {any} The receiver to be used for the function call. -* `...args` {any} Optional arguments to pass to the function. - -Call the provided function with the provided arguments in the execution context -of the async resource. This will establish the context, trigger the AsyncHooks -before callbacks, call the function, trigger the AsyncHooks after callbacks, and -then restore the original execution context. - -### `asyncResource.emitDestroy()` - -* Returns: {AsyncResource} A reference to `asyncResource`. - -Call all `destroy` hooks. This should only ever be called once. An error will -be thrown if it is called more than once. This **must** be manually called. If -the resource is left to be collected by the GC then the `destroy` hooks will -never be called. - -### `asyncResource.asyncId()` - -* Returns: {number} The unique `asyncId` assigned to the resource. - -### `asyncResource.triggerAsyncId()` - -* Returns: {number} The same `triggerAsyncId` that is passed to the - `AsyncResource` constructor. - - - -### Using `AsyncResource` for a `Worker` thread pool - -The following example shows how to use the `AsyncResource` class to properly -provide async tracking for a [`Worker`][] pool. Other resource pools, such as -database connection pools, can follow a similar model. - -Assuming that the task is adding two numbers, using a file named -`task_processor.js` with the following content: - -```mjs -import { parentPort } from 'node:worker_threads'; -parentPort.on('message', (task) => { - parentPort.postMessage(task.a + task.b); -}); -``` - -```cjs -const { parentPort } = require('node:worker_threads'); -parentPort.on('message', (task) => { - parentPort.postMessage(task.a + task.b); -}); -``` - -a Worker pool around it could use the following structure: - -```mjs -import { AsyncResource } from 'node:async_hooks'; -import { EventEmitter } from 'node:events'; -import path from 'node:path'; -import { Worker } from 'node:worker_threads'; - -const kTaskInfo = Symbol('kTaskInfo'); -const kWorkerFreedEvent = Symbol('kWorkerFreedEvent'); - -class WorkerPoolTaskInfo extends AsyncResource { - constructor(callback) { - super('WorkerPoolTaskInfo'); - this.callback = callback; - } - - done(err, result) { - this.runInAsyncScope(this.callback, null, err, result); - this.emitDestroy(); // `TaskInfo`s are used only once. - } -} - -export default class WorkerPool extends EventEmitter { - constructor(numThreads) { - super(); - this.numThreads = numThreads; - this.workers = []; - this.freeWorkers = []; - this.tasks = []; - - for (let i = 0; i < numThreads; i++) - this.addNewWorker(); - - // Any time the kWorkerFreedEvent is emitted, dispatch - // the next task pending in the queue, if any. - this.on(kWorkerFreedEvent, () => { - if (this.tasks.length > 0) { - const { task, callback } = this.tasks.shift(); - this.runTask(task, callback); - } - }); - } - - addNewWorker() { - const worker = new Worker(new URL('task_processor.js', import.meta.url)); - worker.on('message', (result) => { - // In case of success: Call the callback that was passed to `runTask`, - // remove the `TaskInfo` associated with the Worker, and mark it as free - // again. - worker[kTaskInfo].done(null, result); - worker[kTaskInfo] = null; - this.freeWorkers.push(worker); - this.emit(kWorkerFreedEvent); - }); - worker.on('error', (err) => { - // In case of an uncaught exception: Call the callback that was passed to - // `runTask` with the error. - if (worker[kTaskInfo]) - worker[kTaskInfo].done(err, null); - else - this.emit('error', err); - // Remove the worker from the list and start a new Worker to replace the - // current one. - this.workers.splice(this.workers.indexOf(worker), 1); - this.addNewWorker(); - }); - this.workers.push(worker); - this.freeWorkers.push(worker); - this.emit(kWorkerFreedEvent); - } - - runTask(task, callback) { - if (this.freeWorkers.length === 0) { - // No free threads, wait until a worker thread becomes free. - this.tasks.push({ task, callback }); - return; - } - - const worker = this.freeWorkers.pop(); - worker[kTaskInfo] = new WorkerPoolTaskInfo(callback); - worker.postMessage(task); - } - - close() { - for (const worker of this.workers) worker.terminate(); - } -} -``` - -```cjs -const { AsyncResource } = require('node:async_hooks'); -const { EventEmitter } = require('node:events'); -const path = require('node:path'); -const { Worker } = require('node:worker_threads'); - -const kTaskInfo = Symbol('kTaskInfo'); -const kWorkerFreedEvent = Symbol('kWorkerFreedEvent'); - -class WorkerPoolTaskInfo extends AsyncResource { - constructor(callback) { - super('WorkerPoolTaskInfo'); - this.callback = callback; - } - - done(err, result) { - this.runInAsyncScope(this.callback, null, err, result); - this.emitDestroy(); // `TaskInfo`s are used only once. - } -} - -class WorkerPool extends EventEmitter { - constructor(numThreads) { - super(); - this.numThreads = numThreads; - this.workers = []; - this.freeWorkers = []; - this.tasks = []; - - for (let i = 0; i < numThreads; i++) - this.addNewWorker(); - - // Any time the kWorkerFreedEvent is emitted, dispatch - // the next task pending in the queue, if any. - this.on(kWorkerFreedEvent, () => { - if (this.tasks.length > 0) { - const { task, callback } = this.tasks.shift(); - this.runTask(task, callback); - } - }); - } - - addNewWorker() { - const worker = new Worker(path.resolve(__dirname, 'task_processor.js')); - worker.on('message', (result) => { - // In case of success: Call the callback that was passed to `runTask`, - // remove the `TaskInfo` associated with the Worker, and mark it as free - // again. - worker[kTaskInfo].done(null, result); - worker[kTaskInfo] = null; - this.freeWorkers.push(worker); - this.emit(kWorkerFreedEvent); - }); - worker.on('error', (err) => { - // In case of an uncaught exception: Call the callback that was passed to - // `runTask` with the error. - if (worker[kTaskInfo]) - worker[kTaskInfo].done(err, null); - else - this.emit('error', err); - // Remove the worker from the list and start a new Worker to replace the - // current one. - this.workers.splice(this.workers.indexOf(worker), 1); - this.addNewWorker(); - }); - this.workers.push(worker); - this.freeWorkers.push(worker); - this.emit(kWorkerFreedEvent); - } - - runTask(task, callback) { - if (this.freeWorkers.length === 0) { - // No free threads, wait until a worker thread becomes free. - this.tasks.push({ task, callback }); - return; - } - - const worker = this.freeWorkers.pop(); - worker[kTaskInfo] = new WorkerPoolTaskInfo(callback); - worker.postMessage(task); - } - - close() { - for (const worker of this.workers) worker.terminate(); - } -} - -module.exports = WorkerPool; -``` - -Without the explicit tracking added by the `WorkerPoolTaskInfo` objects, -it would appear that the callbacks are associated with the individual `Worker` -objects. However, the creation of the `Worker`s is not associated with the -creation of the tasks and does not provide information about when tasks -were scheduled. - -This pool could be used as follows: - -```mjs -import WorkerPool from './worker_pool.js'; -import os from 'node:os'; - -const pool = new WorkerPool(os.availableParallelism()); - -let finished = 0; -for (let i = 0; i < 10; i++) { - pool.runTask({ a: 42, b: 100 }, (err, result) => { - console.log(i, err, result); - if (++finished === 10) - pool.close(); - }); -} -``` - -```cjs -const WorkerPool = require('./worker_pool.js'); -const os = require('node:os'); - -const pool = new WorkerPool(os.availableParallelism()); - -let finished = 0; -for (let i = 0; i < 10; i++) { - pool.runTask({ a: 42, b: 100 }, (err, result) => { - console.log(i, err, result); - if (++finished === 10) - pool.close(); - }); -} -``` - -### Integrating `AsyncResource` with `EventEmitter` - -Event listeners triggered by an [`EventEmitter`][] may be run in a different -execution context than the one that was active when `eventEmitter.on()` was -called. - -The following example shows how to use the `AsyncResource` class to properly -associate an event listener with the correct execution context. The same -approach can be applied to a [`Stream`][] or a similar event-driven class. - -```mjs -import { createServer } from 'node:http'; -import { AsyncResource, executionAsyncId } from 'node:async_hooks'; - -const server = createServer((req, res) => { - req.on('close', AsyncResource.bind(() => { - // Execution context is bound to the current outer scope. - })); - req.on('close', () => { - // Execution context is bound to the scope that caused 'close' to emit. - }); - res.end(); -}).listen(3000); -``` - -```cjs -const { createServer } = require('node:http'); -const { AsyncResource, executionAsyncId } = require('node:async_hooks'); - -const server = createServer((req, res) => { - req.on('close', AsyncResource.bind(() => { - // Execution context is bound to the current outer scope. - })); - req.on('close', () => { - // Execution context is bound to the scope that caused 'close' to emit. - }); - res.end(); -}).listen(3000); -``` - -[`AsyncResource`]: #class-asyncresource -[`EventEmitter`]: events.md#class-eventemitter -[`Stream`]: stream.md#stream -[`Worker`]: worker_threads.md#class-worker -[`util.promisify()`]: util.md#utilpromisifyoriginal