UNPKG

terriajs

Version:

Geospatial data visualization platform.

156 lines 5.18 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { action, computed, observable, runInAction, makeObservable } from "mobx"; import Result from "./Result"; import TerriaError from "./TerriaError"; /** * * The AsyncLoader class provides a way to memoize (of sorts) async requests. In a `forceLoadX` function you should load from an asynchronous service, transform the data into something that can be stored in 1 or multiple observables and then set those observables. * * It works by calling `loadCallback` in `@computed loadKeepAlive`. This `@computed` will update if observables change that were used in `loadCallback()`.Because we are using a `@computed` in this way - it is **very important** that no changes to `observables` are made **before an async call**. * * A **correct** example: * ```ts * async function loadX() { * const url = this.someObservableUrl * const someData = await loadText(url) * runInAction(() => this.someOtherObservable = someData) * } * ``` * * This function will only be called *again* when `someObservableUrl` changes. * * ------------------------ * * If there is any synchronous processing present it should be pulled out of forceLoadX and placed into 1 or multiple computeds. * * An **incorrect** example: * ```ts * async function loadX() { * const arg = this.someObservable * const someData = someSynchronousFn(arg) * runInAction(() => this.someOtherObservable = someData) * } * ``` * * Instead this should be in a `@computed`: * * ```ts * @computed * get newComputed { * return someSynchronousFn(this.someObservable); * } * ``` * * ------------------------ * * **Other tips**: * * - You can not nest together `AsyncLoaders`. * * **Examples**: * * See: * - `MappableMixin` * - `CatalogMemberMixin` */ export default class AsyncLoader { loadCallback; disposeCallback; _isLoading = false; _result = undefined; _forceReloadCount = 0; _promise = undefined; get loadKeepAlive() { // We don't do much with _forceReloadCount directly, but by accessing it // we will cause a new load to be triggered when it changes. If it's value // is -1, we don't call the `loadCallback` at all, so this keepAlive'd // observable will no longer depend on anything, which avoids creating // a memory leak when this loader is no longer needed. if (this._forceReloadCount < 0) { if (this.disposeCallback) { return this.disposeCallback(); } else { return Promise.resolve(); } } return this.loadCallback(); } constructor( /** {@see AsyncLoader} */ loadCallback, disposeCallback) { this.loadCallback = loadCallback; this.disposeCallback = disposeCallback; makeObservable(this); } /** * Gets a value indicating whether we are currently loading. */ get isLoading() { return this._isLoading; } get result() { return this._result; } async load(forceReload = false) { if (forceReload) { runInAction(() => ++this._forceReloadCount); } const newPromise = this.loadKeepAlive; if (newPromise !== this._promise) { if (this._promise) { // TODO - cancel old promise //this._metadataPromise.cancel(); } this._promise = newPromise; runInAction(() => { this._isLoading = true; }); } let error; try { await newPromise; } catch (e) { error = TerriaError.from(e); } runInAction(() => { this._result = Result.none(error); this._isLoading = false; }); return this._result; } /** * Disposes this loader, allowing the loaded resources to be garbage collected. * The loader can be resurrected and forced to load again by calling * `load(true)`. */ dispose() { this._forceReloadCount = -1; this.load(); } } __decorate([ observable ], AsyncLoader.prototype, "_isLoading", void 0); __decorate([ observable ], AsyncLoader.prototype, "_result", void 0); __decorate([ observable ], AsyncLoader.prototype, "_forceReloadCount", void 0); __decorate([ computed({ keepAlive: true }) ], AsyncLoader.prototype, "loadKeepAlive", null); __decorate([ action ], AsyncLoader.prototype, "load", null); __decorate([ action ], AsyncLoader.prototype, "dispose", null); //# sourceMappingURL=AsyncLoader.js.map