{"id":13657461,"url":"https://github.com/fynyky/reactor.js","last_synced_at":"2026-02-21T12:03:13.115Z","repository":{"id":8364114,"uuid":"9929981","full_name":"fynyky/reactor.js","owner":"fynyky","description":"Simple reactive programming without a framework","archived":false,"fork":false,"pushed_at":"2025-11-21T21:39:29.000Z","size":853,"stargazers_count":800,"open_issues_count":2,"forks_count":52,"subscribers_count":27,"default_branch":"master","last_synced_at":"2026-01-23T23:28:00.818Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fynyky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2013-05-08T06:37:55.000Z","updated_at":"2025-09-11T15:11:15.000Z","dependencies_parsed_at":"2024-01-13T10:41:39.831Z","dependency_job_id":"feb169d8-d9fb-4576-9e0f-35a81aef9c95","html_url":"https://github.com/fynyky/reactor.js","commit_stats":{"total_commits":238,"total_committers":4,"mean_commits":59.5,"dds":"0.012605042016806678","last_synced_commit":"3d9a42a4ae5f3889a698f211fa0ecdef94399a83"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/fynyky/reactor.js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fynyky%2Freactor.js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fynyky%2Freactor.js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fynyky%2Freactor.js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fynyky%2Freactor.js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fynyky","download_url":"https://codeload.github.com/fynyky/reactor.js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fynyky%2Freactor.js/sbom","scorecard":{"id":415598,"data":{"date":"2025-08-11","repo":{"name":"github.com/fynyky/reactor.js","commit":"27d3ce2f4ec581331d1915b38bf3da7c3bbec0b3"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":1,"reason":"Found 1/9 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/npm-publish.yml:1","Warn: no topLevel permission defined: .github/workflows/pull-request-test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/npm-publish.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/fynyky/reactor.js/npm-publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/npm-publish.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/fynyky/reactor.js/npm-publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pull-request-test.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/fynyky/reactor.js/pull-request-test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pull-request-test.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/fynyky/reactor.js/pull-request-test.yml/master?enable=pin","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   2 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":2,"reason":"8 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-xq7p-g2vc-g82p","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-7hpj-7hhx-2fgx","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-76p7-773f-r4q5"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T23:45:27.038Z","repository_id":8364114,"created_at":"2025-08-18T23:45:27.038Z","updated_at":"2025-08-18T23:45:27.038Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29680147,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T11:29:27.227Z","status":"ssl_error","status_checked_at":"2026-02-21T11:29:20.292Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T05:00:43.301Z","updated_at":"2026-02-21T12:03:13.096Z","avatar_url":"https://github.com/fynyky.png","language":"JavaScript","readme":"Reactor.js\n==========\n\nReactor.js is a simple library for [reactive programming](http://en.wikipedia.org/wiki/Reactive_programming). It provides:\n- `Reactor` objects that store reactive variables\n- `Observer` functions that automatically track the reactive variables they use and retrigger when any of these variables are updated\n\nHere's a quick example of what Reactor.js does:\n```javascript\nconst reactor = new Reactor()\nreactor.foo = 'bar'\nconst observer = new Observer(() =\u003e {\n  console.log('foo is ', reactor.foo)\n})\nobserver() // prints \"foo is bar\"\nreactor.foo = 'moo' // prints \"foo is moo\"\n```\n- `Reactor` objects work like normal objects that you can set and get properties on\n- `Observer` functions work like normal functions that you can define and call\n- When an `Observer` reads a `Reactor`, it registers itself as a dependent\n- When a `Reactor` is updated, it automatically retriggers the dependent `Observer` functions\n\nReactor.js is designed to be unobtrusive and unopinionated:\n- No special syntax to learn. Everything is just plain JavaScript\n- There is no need to manually declare listeners or bindings. Reactor.js automatically keeps track of all that for you\n- It imposes no particular structure on your code. Any variable can be easily replaced with a reactive one without changing the rest of your codebase\n\nInstallation\n------------\n\nReactor.js is [available on npm](https://npmjs.org/package/reactorjs). Install it by running:\n```\n$ npm install reactorjs\n```\n\nImport it using:\n```javascript\nimport { Reactor, Observer, hide, batch, shuck } from 'reactorjs'\n```\n\nIt is also available directly from [unpkg](unpkg.com). You can import it in JavaScript using:\n```javascript\nimport { Reactor, Observer, hide, batch, shuck } from 'https://unpkg.com/reactorjs'\n```\n\nReactors\n--------\n\nA `Reactor` is an object wrapper that automatically tracks `Observer` functions that read its properties and notifies the observers when those properties are updated.\n\nYou create a new reactor by calling its constructor:\n```javascript\nconst reactor = new Reactor()\n```\n\nYou can also wrap an existing object with a reactor by passing it to the constructor. Changes to the reactor are passed through to the underlying object:\n```javascript\nconst reactor = new Reactor({\n  foo: \"bar\"\n})\n```\n\nReactors behave mostly like plain JavaScript objects:\n```javascript\nconst reactor = new Reactor({\n  foo: \"bar\"\n})\n// You can get and set properties as usual\nreactor.foo // \"bar\"\nreactor.cow = \"moo\"\n// defineProperty works normally as well\nObject.defineProperty(reactor, \"milk\", {\n  get() { return \"chocolate\" }\n})\nreactor.milk // \"chocolate\"\n// delete works too\ndelete reactor.foo\nreactor.foo // undefined\n```\n\nThe key difference of `Reactor` objects is that they track when one of their properties is read by an `Observer` function and will notify that observer when the property is updated:\n\n```javascript\nconst reactor = new Reactor({ foo: \"bar\" })\n\nnew Observer(() =\u003e {\n  console.log(\"foo is \", reactor.foo)\n})() // prints \"foo is bar\"\n\nreactor.foo = \"moo\" // prints \"foo is moo\"\n\nObject.defineProperty(reactor, \"foo\", {\n  get() { return \"meow\" }\n}) // prints \"foo is meow\"\n\ndelete reactor.foo // prints \"foo is undefined\"\n```\n\nTracking is property-specific, so observers will not trigger if a different property is updated:\n```javascript\nconst reactor = new Reactor({\n  foo: \"bar\",\n  moo: \"mar\"\n})\n\nnew Observer(() =\u003e {\n  console.log(\"foo tracker is now\", reactor.foo)\n})() // prints \"foo tracker is now bar\"\n\nnew Observer(() =\u003e {\n  console.log(\"moo tracker is now\", reactor.moo)\n})() // prints \"moo tracker is now mar\"\n\nreactor.foo = \"bar2\" // prints \"foo tracker is now bar2\"\nreactor.moo = \"mar2\" // prints \"moo tracker is now mar2\"\nreactor.goo = \"goop\" // does not trigger any observers\n```\n\nIf reading a reactor's property returns an object, that object is recursively wrapped in a reactor before being returned. This allows observers to track dependencies in nested objects easily:\n```javascript\nconst reactor = new Reactor({\n  outer: {\n    inner: \"cake\"\n  }\n})\n\nnew Observer(() =\u003e {\n  console.log(\"inner value is \", reactor.outer.inner)\n})() // prints \"inner value is cake\"\n```\n\nReactors are implemented using [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects. This means reactors created from scratch typecheck as Reactors, but reactors created from an existing object typecheck as the original object:\n\n```javascript\nconst baseReactor = new Reactor()\nbaseReactor instanceof Reactor // true\nconst mapReactor = new Reactor(new Map())\nmapReactor instanceof Reactor // false\nmapReactor instanceof Map // true\n```\n\nThis also has implications for native objects or objects that use private properties. Since proxies can't access native or private properties, some methods will fail. To work around this, we provide the `shuck` function which returns a reactor's internal object:\n\n```javascript\n// Native object example\nconst mapReactor = new Reactor(new Map())\nMap.prototype.keys.apply(mapReactor) // throws an error\nMap.prototype.keys.apply(shuck(mapReactor)) // works fine\n```\n\nObservers\n---------\n\nAn `Observer` is like a normal function that you can define and call. When an `Observer` reads from a `Reactor`, it automatically tracks that dependency, and when that reactor's property is updated, it automatically triggers the observer again.\n\n`Observer` functions are created by passing a function to its constructor:\n```javascript\nconst observer = new Observer(() =\u003e {\n  console.log(\"hello world\")\n})\nobserver() // prints \"hello world\" and starts the observer\n```\n\nFor brevity, observers can also be created and instantly executed like this:\n```javascript\nnew Observer(() =\u003e {\n  console.log(\"hello world\")\n})() // prints \"hello world\" and starts the observer\n```\n\nWhen an `Observer` reads a `Reactor` property, it gets saved as a dependent. When that property is updated, it notifies the observer which reruns its function. This happens automatically without any need to manually declare dependencies:\n```javascript\nconst reactor = new Reactor()\nnew Observer(() =\u003e {\n  console.log(\"reactor.foo is \", reactor.foo)\n})() // prints \"reactor.foo is undefined\"\n\nreactor.foo = \"bar\" // prints \"reactor.foo is bar\"\n```\n\nAn observer's dependencies are dynamically determined. Only the dependencies actually read in the last execution of an observer can trigger it again. This means that reactor reads that are only conditionally used will not trigger the observer unnecessarily:\n```javascript\nconst reactor = new Reactor({\n  a: true,\n  b: \"bee\",\n  c: \"cee\"\n})\nnew Observer(() =\u003e {\n  if (reactor.a) {\n    console.log(\"reactor.b is \", reactor.b)\n  } else {\n    console.log(\"reactor.c is \", reactor.c)\n  }\n})() // prints \"reactor.b is bee\"\n\nreactor.b = \"boop\" // prints \"reactor.b is boop\"\nreactor.c = \"cat\" // does not trigger the observer\n\nreactor.a = false // prints \"reactor.c is cat\"\nreactor.b = \"blue\" // does not trigger the observer\nreactor.c = \"cheese\" // prints \"reactor.c is cheese\"\n```\n\nAn observer's results are themselves observable via either the `value` property or by triggering the observer via `observer()` and using the return value. This allows you to chain observers together:\n```javascript\nconst reactor = new Reactor({ foo: 'bar' })\nconst capitalizer = new Observer(() =\u003e {\n  return reactor.foo.toUpperCase()\n})()\nconst printer = new Observer(() =\u003e {\n  console.log(capitalizer.value)\n})() // prints 'BAR'\nreactor.foo = 'baz' // prints 'BAZ'\n```\n\nThis also works:\n```javascript\nconst reactor = new Reactor({ foo: 'bar' })\nconst capitalizer = new Observer(() =\u003e {\n  return reactor.foo.toUpperCase()\n}) // Did not start the observer here\nconst printer = new Observer(() =\u003e {\n  // Manually calls capitalizer like a function which activates it\n  // As well as accesses its return value as a dependency\n  console.log(capitalizer())\n})() // starts printer which starts capitalizer\nreactor.foo = 'baz' // prints 'BAZ'\n```\n\nYou can stop an observer by calling `stop()` on the returned observer object. This clears any existing dependencies and prevents triggering. You can restart the observer by calling `start()`. Starting is idempotent, so calling `start()` on an already running observer will have no effect:\n```javascript\nconst reactor = new Reactor()\nconst observer = new Observer(() =\u003e {\n  console.log(reactor.foo)\n})() // prints \"undefined\"\n\nreactor.foo = \"bar\" // prints \"bar\"\n\nobserver.stop()\n\nreactor.foo = \"cheese\" // does not trigger the observer\n\nobserver.start() // prints \"cheese\"\nobserver.start() // No effect\nobserver.start() // No effect\nobserver.start() // No effect\n\nreactor.foo = \"moo\" // prints \"moo\"\n```\n\nFor convenience, you can call an observer to execute like a normal function. This works regardless of whether the observer is stopped. Doing so starts the observer up again:\n\n```javascript\nconst reactor = new Reactor({ foo: \"hello\" })\nconst observer = new Observer(() =\u003e {\n  console.log(reactor.foo)\n})() // prints \"hello\"\nreactor.foo = \"hi\" // prints \"hi\"\nobserver() // prints \"hi\" again\n\nobserver.stop()\nreactor.foo = \"hola\" // does not trigger the observer since it's stopped\nobserver() // prints \"hola\"\n```\n\nLike normal functions, observers can expect and be called with arguments. They remember the arguments from the last time they were called and reuse them when automatically triggered:\n\n```javascript\nconst parameterizedObserver = new Observer((arg1, arg2) =\u003e {\n  console.log(reactor.foo + arg1 + arg2)\n})\nparameterizedObserver('beep', 'bop') // prints bazbeepbop\nreactor.foo = 'bla' // prints blabeepbop\n```\n\nObservers can also use and remember the last `this` context. Note that just like normal functions, for the `this` context to be bound to the holding object, it needs to be defined with the traditional `function` keyword instead of ES6 arrow functions:\n\n```javascript\nconst holdingObject = {\n  name: 'Mario',\n  greet: new Observer(function () { // Need to use `function`\n    console.log(\"Hello \" + reactor.foo + \" itsa me \" + this.name)\n  })\n}\nholdingObject.greet() // prints \"Hello bla itsa me Mario\"\nreactor.foo = 'bonk' // prints \"Hello bonk itsa me Mario\"\nholdingObject.name = 'Luigi' // prints nothing since holdingObject is not a Reactor\n```\n\nIf you ever need to access the raw function the observer is wrapping, you do so by using `shuck`:\n\n```javascript\nconst myFunction = () =\u003e {}\nconst observer = new Observer(myFunction)\nmyFunction === shuck(observer) // true\n```\n\n### Hide\n\nSometimes you might want to read from a reactor without becoming dependent on it. A common case for this is when using array modification methods. These often also read from the array in order to do the modification:\n```javascript\nconst taskList = new Reactor([\"a\", \"b\", \"c\", \"d\"])\n\n// Creating the following observer will cause an infinite loop\n// because it both reads from and modifies the length property of taskList\n// As a result, it triggers itself in the middle of execution\n// This loop is detected and creates an exception\nnew Observer(() =\u003e {\n  // Even though we only want to modify the array\n  // pop() also reads the length property of the array\n  console.log(taskList.pop())\n})()\n```\n\nIn these cases, you can use \"hide\" to shield a block of code from creating dependencies. It takes a function and any reactor properties read inside that function will not be set as dependencies. `hide` also passes through the return value of its function for syntactic simplicity:\n```javascript\nconst taskList = new Reactor([\"a\", \"b\", \"c\", \"d\"])\n\nnew Observer(() =\u003e {\n  console.log(\n    // Because we wrap the pop() call in a hide block\n    // it does not create a dependency on the length property\n    // unlike our previous example\n    hide(() =\u003e taskList.pop())\n  )\n})() // prints \"d\"\n\ntaskList.push(\"e\") // does not trigger the observer\n```\n\nNote that only the reads inside the hide block are shielded from creating dependencies. The rest of the observe block still creates dependencies as normal.\n\n### Batching\nOne problem with automatic watchers is that you might end up with multiple repeated triggering when you're updating a lot of information all at once. The following code shows an example where you want to update multiple properties, but each property update prematurely triggers the observer since you are not done updating yet:\n\n```javascript\nconst person = new Reactor({\n  firstName: \"Anakin\",\n  lastName: \"Skywalker\",\n  faction: \"Jedi\",\n  rank: \"Knight\"\n})\n\n// This observer tracks multiple properties\n// and so will be triggered when any of the properties get updated\nconst observer = new Observer(() =\u003e {\n  console.log(\n    \"I am \" +\n    person.firstName +\n    \" \" +\n    person.lastName +\n    \", \" +\n    person.faction +\n    \" \" +\n    person.rank\n  )\n})() // prints \"I am Anakin Skywalker, Jedi Knight\"\n\n// The following updates will each trigger the observer even though we only\n// want to trigger the observer once all the updates are complete\nperson.firstName = \"Darth\" // prints \"I am Darth Skywalker, Jedi Knight\"\nperson.lastName = \"Vader\" // prints \"I am Darth Vader, Jedi Knight\"\nperson.faction = \"Sith\" // prints \"I am Darth Vader, Sith Knight\"\nperson.rank = \"Lord\" // prints \"I am Darth Vader, Sith Lord\"\n```\n\nThe `batch` function is provided to allow you to batch multiple updates together and only trigger the appropriate observers once at the end of the batch block. So the last part of the previous example can be turned into:\n```javascript\n// batch postpones any observer triggers that originate from inside it\n// Triggers are deduplicated so any observer is triggered at most once\nbatch(() =\u003e {\n  // None of the following updates will trigger the observer yet\n  person.firstName = \"Darth\"\n  person.lastName = \"Vader\"\n  person.faction = \"Sith\"\n  person.rank = \"Lord\"\n}) // prints \"I am Darth Vader, Sith Lord\"\n```\n\nThis is useful when you are making multiple data updates and want to avoid showing an \"incomplete\" view of the data to observers.\n\nNote that only the observer triggering is postponed until the end. The actual reactor properties are updated in place as expected. This means that you can have other logic with read-what-you-write semantics within the observer block working just fine.\n\nSummary\n-------\n```javascript\nimport { Reactor, Observer, hide, batch, shuck } from 'reactorjs'\n\nconst reactor = new Reactor({ foo: 'bar' })\nconst observer = new Observer(() =\u003e {\n  const result = 'reactor.foo is ' + reactor.foo // Sets a dependency on foo\n  console.log(result)\n  return result\n})\nobserver() // prints 'reactor.foo is bar' and starts the observer\nreactor.foo = 'baz' // prints 'reactor.foo is baz'\n\nobserver.stop()\nreactor.foo = 'qux' // prints nothing since observer is stopped\n\nobserver.start() // prints 'reactor.foo is baz'\nobserver.start() // prints nothing since observer is already started\nobserver() // prints 'reactor.foo is baz' even if it is already running\n\n// Observer return values are themselves observable\nconst trailingObserver = new Observer(() =\u003e {\n  const result = 'Did you hear: ' + observer.value\n  console.log(result)\n})\ntrailingObserver() // prints 'Did you hear: reactor.foo is baz'\nreactor.foo = 'blorp' // prints 'reactor.foo is blorp' from observer\n                      // also prints 'Did you hear: reactor.foo is blorp' from trailingObserver\n\n// Observers can be given parameters and remember these parameters when triggered\nconst parameterizedObserver = new Observer((arg1, arg2) =\u003e {\n  console.log(reactor.foo + arg1 + arg2)\n})\nparameterizedObserver('beep', 'bop') // prints bazbeepbop\nreactor.foo = 'bla' // prints blabeepbop\n\n// Observers can also access and remember the last `this` context\nconst holdingObject = {\n  name: 'Mario',\n  greet: new Observer(function () { // Need to use traditional functions instead of arrow functions\n    console.log(\"Hello \" + reactor.foo + \" itsa me \" + this.name)\n  })\n}\nholdingObject.greet() // prints \"Hello bla itsa me Mario\"\nreactor.foo = 'bonk' // prints \"Hello bonk itsa me Mario\"\nholdingObject.name = 'Luigi' // prints nothing since holdingObject is not a Reactor\n\n// hide allows you to avoid particular dependencies in an observer\n// This is useful especially when using Array methods that both read and write\nreactor.ticker = 1\nreactor.names = [\"Alice\", \"Bob\", \"Charles\", \"David\"]\nconst partialObserver = new Observer(() =\u003e {\n  if (reactor.ticker) {\n    // hide passes through the return value of its block\n    const next = hide(() =\u003e reactor.names.pop())\n    console.log(\"next \", next)\n  }\n})\npartialObserver() // prints \"next David\"\nreactor.ticker = 2 // prints \"next Charles\"\nreactor.names.push(\"Elsie\") // Will not trigger the observer\n\n// batch postpones any observer triggers until it is complete\n// This allows grouping updates together\nconst person = new Reactor({\n  firstName: 'Clark',\n  lastName: 'Kent'\n})\nnew Observer(() =\u003e {\n  console.log('Look its ' + person.firstName + ' ' + person.lastName)\n})() // prints 'Look its Clark Kent'\nbatch(() =\u003e {\n  // None of the following updates will trigger the observer yet\n  person.firstName = \"Bruce\"\n  person.lastName = \"Wayne\"\n}) // prints 'Look its Bruce Wayne'\n\n// shuck removes the Reactor layer and returns the base object\n// This is necessary for some native objects which don't work with proxies\nconst mapReactor = new Reactor(new Map())\nMap.prototype.keys.call(mapReactor) // throws an Error\nMap.prototype.keys.call(shuck(mapReactor)) // works fine\n```\n\nDevelopment \u0026 Testing\n---------------------\nTests are stored in `test.js` to be run using Mocha.\n\nRun `npm install` to install the dev dependencies.\n\nTo run the tests, run `npm test`.\n","funding_links":[],"categories":["Uncategorized","🌐 Web Development - Frontend","JavaScript"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffynyky%2Freactor.js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffynyky%2Freactor.js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffynyky%2Freactor.js/lists"}