{"id":21721167,"url":"https://github.com/danielearwicker/computed-async-mobx","last_synced_at":"2026-02-28T07:34:00.503Z","repository":{"id":14831210,"uuid":"77180421","full_name":"danielearwicker/computed-async-mobx","owner":"danielearwicker","description":"Define a computed by returning a Promise","archived":false,"fork":false,"pushed_at":"2023-01-07T02:19:56.000Z","size":815,"stargazers_count":166,"open_issues_count":14,"forks_count":6,"subscribers_count":9,"default_branch":"master","last_synced_at":"2026-02-15T09:21:06.048Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/danielearwicker.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-22T22:37:58.000Z","updated_at":"2026-02-13T12:13:10.000Z","dependencies_parsed_at":"2023-01-11T18:54:48.230Z","dependency_job_id":null,"html_url":"https://github.com/danielearwicker/computed-async-mobx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/danielearwicker/computed-async-mobx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielearwicker%2Fcomputed-async-mobx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielearwicker%2Fcomputed-async-mobx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielearwicker%2Fcomputed-async-mobx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielearwicker%2Fcomputed-async-mobx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielearwicker","download_url":"https://codeload.github.com/danielearwicker/computed-async-mobx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielearwicker%2Fcomputed-async-mobx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29927585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T19:37:42.220Z","status":"online","status_checked_at":"2026-02-28T02:00:07.010Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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-11-26T02:14:26.363Z","updated_at":"2026-02-28T07:34:00.485Z","avatar_url":"https://github.com/danielearwicker.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# computed-async-mobx\n_Define a computed by returning a Promise_\n\n[![Build Status](https://travis-ci.org/danielearwicker/computed-async-mobx.svg?branch=master)](https://travis-ci.org/danielearwicker/computed-async-mobx)\n[![Coverage Status](https://coveralls.io/repos/danielearwicker/computed-async-mobx/badge.svg?branch=master\u0026service=github)](https://coveralls.io/github/danielearwicker/computed-async-mobx?branch=master)\n\n*\"People starting with MobX tend to use reactions [*autorun*] too often. The golden rule is: if you want to create a value based on the current state, use computed.\"* - [MobX - Concepts \u0026 Principles](http://mobxjs.github.io/mobx/intro/concepts.html)\n\n# About MobX 6\n\nAn attempt was made, then abandoned, to quick-fix this library to work with MobX 6, but it didn't work out (to put it mildly). Any suggestions for how to fix it are welcome! For now it only works with MobX versions 3.0 to 5.x.\n\nIt's possible the API may need to be cut down to something a bit simpler with fewer guarantees but still achieving the basic goal.\n\n# What is this for?\n\nA `computed` in MobX is defined by a function, which consumes other observable values and is automatically re-evaluated, like a spreadsheet cell containing a calculation.\n\n```ts\n@computed get creditScore() {\n    return this.scoresByUser[this.userName];\n}\n```\nHowever, it has to be a synchronous function body. What if you want to do something asynchronous? e.g. get something from the server. That's where this little extension comes in:\n\n```ts\ncreditScore = promisedComputed(0, async () =\u003e {\n    const response = await fetch(`users/${this.userName}/score`);\n    const data = await response.json();\n    return data.score;\n});\n```\n\n[Further explanation, rationale, etc.](../../wiki)\n\n# New in Version 3.0.0...\n\nThere is a completely new API, much more modular and made of simple, testable pieces. The\nold API is deprecated, though is still available for now. First here's how the current \nfeatures work. Stay tuned for a migration guide below.\n\n----\n\n## asyncComputed\n\nThis is the most capable function. It is actually just a composition of two simpler functions,\n`promisedComputed` and `throttledComputed`, described below.\n\n### Parameters\n\n- `init` - the value to assume until the first genuine value is returned\n- `delay` - the minimum time in milliseconds to wait between creating new promises\n- `compute` - the function to evaluate to get a promise (or plain value)\n\n### Returns\n\nA Mobx-style getter, i.e. an object with a `get` function that returns the current value. It\nis an observable, so it can be used from other MobX contexts. It *cannot* be used outside\nMobX reactive contexts (it throws an exception if you attempt it).\n\nThe returned object also has a `busy` property that is true while a promise is still pending.\nIt also has a `refresh` method that can be called to force a new promise to be requested\nimmediately (bypassing the delay time).\n\n**New in 4.2.0:** there is also a method `getNonReactive()` which can be used outside reactive\ncontexts. It is a convenience for writing unit tests. Note that it will return the most recent\nvalue that was computed while the `asyncComputed` was being observed.\n\n### Example\n\n```ts\nfullName = asyncComputed(\"(Please wait...)\", 500, async () =\u003e {\n        const response = await fetch(`users/${this.userName}/info`);\n        const data = await response.json();\n        return data.fullName;\n});\n```\n\nThe value of `fullName.get()` is observable. It will initially return\n`\"(Please wait...)\"` and will later transition to the user's full name.\nIf the `this.userName` property is an observable and is modified, the\n`promisedComputed` will update also, but after waiting at least 500\nmilliseconds.\n\n----\n\n## promisedComputed\n\nLike `asyncComputed` but without the `delay` support. This has the slight advantage\nof being fully synchronous if the `compute` function returns a plain value.\n\n### Parameters\n\n- `init` - the value to assume until the first genuine value is returned\n- `compute` - the function to evaluate to get a promise (or plain value)\n\n### Returns\n\nExactly as `asyncComputed`.\n\n### Example\n\n```ts\nfullName = promisedComputed(\"(Please wait...)\", async () =\u003e {\n    const response = await fetch(`users/${this.userName}/info`);\n    const data = await response.json();\n    return data.fullName;\n});\n```\n\nThe value of `fullName.get()` is observable. It will initially return\n`\"(Please wait...)\"` and will later transition to the user's full name.\nIf the `this.userName` property is an observable and is modified, the\n`promisedComputed` will update also, as soon as possible.\n\n----\n\n## throttledComputed\n\nLike the standard `computed` but with support for delaying for a specified number of \nmilliseconds before re-evaluation. It is like a computed version of the standard \n`autorunAsync`; the advantage is that you don't have to manually dispose it.\n\n(Note that `throttledComputed` has no special functionality for handling promises.)\n\n### Parameters\n\n- `compute` - the function to evaluate to get a plain value\n- `delay` - the minimum time in milliseconds to wait before re-evaluating\n\n### Returns\n\nA Mobx-style getter, i.e. an object with a `get` function that returns the current value. It\nis an observable, so it can be used from other MobX contexts. It can also be used outside\nMobX reactive contexts but (like standard `computed`) it reverts to simply re-evaluating \nevery time you request the value.\n\nIt also has a `refresh` method that *immediately* (synchronously) re-evaluates the function.\n\n### Example\n\n```ts\nfullName = throttledComputed(500, () =\u003e {\n    const data = slowSearchInMemory(this.userName);\n    return data.fullName;\n});\n```\n\nThe value of `fullName.get()` is observable. It will initially return the result of the\nsearch, which happens synchronously the first time. If the `this.userName` property is an\nobservable and is modified, the `throttledComputed` will update also, but after waiting at\nleast 500 milliseconds.\n\n----\n\n## autorunThrottled\n\nMuch like the standard `autorunAsync`, except that the initial run of the function happens\nsynchronously.\n\n(This is used by `throttledComputed` to allow it to be synchronously initialized.)\n\n### Parameters\n\n- `func` - The function to execute in reaction\n- `delay` - The minimum delay between executions\n- `name` - (optional) For MobX debug purposes\n\n### Returns\n\n- a disposal function.\n\nA Mobx-style getter, i.e. an object with a `get` function that returns the current value. It\nis an observable, so it can be used from other MobX contexts. It can also be used outside\nMobX reactive contexts but (like standard `computed`) it reverts to simply re-evaluating \nevery time you request the value.\n\n----\n\n# Installation\n\n    npm install computed-async-mobx\n\n# TypeScript\n\nOf course TypeScript is optional; like a lot of libraries these days, this is a JavaScript \nlibrary that happens to be written in TypeScript. It also has built-in type definitions: no \nneed to `npm install @types/...` anything.\n\n# Acknowledgements\n\nI first saw this idea on the [Knockout.js wiki](https://github.com/knockout/knockout/wiki/Asynchronous-Dependent-Observables) in 2011. [As discussed here](https://smellegantcode.wordpress.com/2015/02/21/knockout-clear-fully-automatic-cleanup-in-knockoutjs-3-3/) it was tricky to make it well-behaved re: memory leaks for a few years.\n\nMobX uses the same (i.e. correct) approach as `ko.pureComputed` from the ground up, and the [Atom](http://mobxjs.github.io/mobx/refguide/extending.html#atoms) class makes it easy to detect when your data transitions \nbetween being observed and not. More recently I realised `fromPromise` in [mobx-utils](https://github.com/mobxjs/mobx-utils) \ncould be used to implement `promisedComputed` pretty directly. If you don't need throttling (`delay` parameter) then all\nyou need is a super-thin layer over existing libraries, which is what `promisedComputed` is.\n\nAlso a :rose: for [Basarat](https://github.com/basarat) for pointing out the need to support strict mode!\n\nThanks to [Daniel Nakov](https://github.com/dnakov) for fixes to support for MobX 4.x.\n\n# Usage\n\nUnlike the normal `computed` feature, `promisedComputed` can't work as a decorator on a property getter. This is because it changes the type of the return value from `PromiseLike\u003cT\u003e` to `T`.\n\nInstead, as in the example above, declare an ordinary property. If you're using TypeScript (or an ES6 transpiler with equivalent support for classes) then you can declare and initialise the property in a class in one statement:\n\n```ts\nclass Person {\n\n     @observable userName: string;\n\n     creditScore = promisedComputed(0, async () =\u003e {\n         const response = await fetch(`users/${this.userName}/score`);\n         const data = await response.json();\n         return data.score; // score between 0 and 1000\n     });\n\n     @computed\n     get percentage() {\n         return Math.round(this.creditScore.get() / 10);\n     }\n}\n```\n\nNote how we can consume the value via the `.get()` function inside another (ordinary) computed and it too will re-evaluate when the score updates.\n\n# { enforceActions: \"always\" }\n\nThis library is transparent with respect to [MobX's strict mode](https://github.com/mobxjs/mobx/blob/gh-pages/docs/refguide/api.md#enforceactions), and since 4.2.0 this is true even of the very strict `\"always\"` mode that doesn't even let you initialize fields of a class outside a reactive context.\n\n# Gotchas\n\nTake care when using `async`/`await`. MobX dependency tracking can only detect you reading data in the first \"chunk\" of a function containing `await`s. It's okay to read data in the expression passed to `await` (as in the above example) because that is evaluated before being passed to the first `await`. But after execution \"returns\" from the first `await` the context is different and MobX doesn't track further reads.\n\nFor example, here we fetch two pieces of data to combine them together:\n\n```ts\nanswer = asyncComputed(0, 1000, async () =\u003e {\n    const part1 = await fetch(this.part1Uri),\n          part2 = await fetch(this.part2Uri);\n    \n    // combine part1 and part2 into a result somehow...\n    return result;\n});\n```\n\nThe properties `part1Uri` and `part2Uri` are ordinary mobx `observable`s (or `computed`s). You'd expect that when either of those values changes, this `asyncComputed` will re-execute. But in fact it can only detect when `part1Uri` changes. When an `async` function is called, only the first part (up to the first `await`) executes immediately, and so that's the only part that MobX will be able to track. The remaining parts execute later on, when MobX has stopped listening.\n\n(Note: the expression on the right of `await` has to be executed before the `await` pauses the function, so the access to `this.part1Uri` is properly detected by MobX).\n\nWe can work around this like so:\n\n```ts\nanswer = asyncComputed(0, 1000, async () =\u003e {\n    const uri1 = this.part1Uri, \n          uri2 = this.part2Uri;\n\n    const part1 = await fetch(uri1),\n          part2 = await fetch(uri2);\n\n    // combine part1 and part2 into a result somehow...\n    return result;\n});\n```\n\nWhen in doubt, move all your gathering of observable values to the start of the `async` function.\n\n# Migration\n\nThe API of previous versions is still available. It was a single `computedAsync` function that had all the\ncapabilities, like a Swiss-Army Knife, making it difficult to test, maintain and use. It also had some\nbuilt-in functionality that could just as easily be provided by user code, which is pointless and only\ncreates obscurity.\n\n- Instead of calling `computedAsync` with a zero `delay`, use `promisedComputed`, which takes no `delay`\n  parameter.\n- Instead of calling `computedAsync` with a non-zero `delay`, use `asyncComputed`.\n- Instead of using the `value` property, call the `get()` function (this is for closer consistency with \n  standard MobX `computed`.)\n- Instead of using `revert`, use the `busy` property to decide when to substitute a different value.\n- The `rethrow` property made `computedAsync` propagate exceptions. There is no need to request this\n  behaviour with `promisedComputed` and `asyncComputed` as they always propagate exceptions.\n- The `error` property computed a substitute value in case of an error. Instead, just do this substitution\n  in your `compute` function.\n\n# Version History\n\nSee [CHANGES.md](CHANGES.md).\n\n# License\n\nMIT, see [LICENSE](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielearwicker%2Fcomputed-async-mobx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielearwicker%2Fcomputed-async-mobx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielearwicker%2Fcomputed-async-mobx/lists"}