diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e3b247f40..65be7e5747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 3.5.0 +* Added extras.disableErrorBoundaries() + # 3.4.0 * Improve Flow support support by exposing typings regularly. Flow will automatically include them now. In your `.flowconfig` will have to remove the import in the `[libs]` section (as it's done [here](https://github.com/mobxjs/mobx/pull/1254#issuecomment-348926416)). Fixes [#1232](https://github.com/mobxjs/mobx/issues/1232). diff --git a/src/core/computedvalue.ts b/src/core/computedvalue.ts index b40307c0a5..220e9723d0 100644 --- a/src/core/computedvalue.ts +++ b/src/core/computedvalue.ts @@ -189,10 +189,14 @@ export class ComputedValue implements IObservable, IComputedValue, IDeriva if (track) { res = trackDerivedFunction(this, this.derivation, this.scope) } else { - try { + if (globalState.disableErrorBoundaries) { res = this.derivation.call(this.scope) - } catch (e) { - res = new CaughtException(e) + } else { + try { + res = this.derivation.call(this.scope) + } catch (e) { + res = new CaughtException(e) + } } } globalState.computationDepth-- @@ -242,18 +246,18 @@ export class ComputedValue implements IObservable, IComputedValue, IDeriva ` WhyRun? computation '${this.name}': * Running because: ${isTracking - ? "[active] the value of this computation is needed by a reaction" - : this.isComputing - ? "[get] The value of this computed was requested outside a reaction" - : "[idle] not running at the moment"} + ? "[active] the value of this computation is needed by a reaction" + : this.isComputing + ? "[get] The value of this computed was requested outside a reaction" + : "[idle] not running at the moment"} ` + (this.dependenciesState === IDerivationState.NOT_TRACKING ? getMessage("m032") : ` * This computation will re-run if any of the following observables changes: ${joinStrings(observing)} ${this.isComputing && isTracking - ? " (... or any observable accessed during the remainder of the current run)" - : ""} + ? " (... or any observable accessed during the remainder of the current run)" + : ""} ${getMessage("m038")} * If the outcome of this computation changes, the following observers will be re-run: diff --git a/src/core/derivation.ts b/src/core/derivation.ts index e6091fccc8..fc42844c10 100644 --- a/src/core/derivation.ts +++ b/src/core/derivation.ts @@ -81,12 +81,16 @@ export function shouldCompute(derivation: IDerivation): boolean { for (let i = 0; i < l; i++) { const obj = obs[i] if (isComputedValue(obj)) { - try { + if (globalState.disableErrorBoundaries) { obj.get() - } catch (e) { - // we are not interested in the value *or* exception at this moment, but if there is one, notify all - untrackedEnd(prevUntracked) - return true + } else { + try { + obj.get() + } catch (e) { + // we are not interested in the value *or* exception at this moment, but if there is one, notify all + untrackedEnd(prevUntracked) + return true + } } // if ComputedValue `obj` actually changed it will be computed and propagated to its observers. // and `derivation` is an observer of `obj` @@ -131,10 +135,14 @@ export function trackDerivedFunction(derivation: IDerivation, f: () => T, con const prevTracking = globalState.trackingDerivation globalState.trackingDerivation = derivation let result - try { + if (globalState.disableErrorBoundaries) { result = f.call(context) - } catch (e) { - result = new CaughtException(e) + } else { + try { + result = f.call(context) + } catch (e) { + result = new CaughtException(e) + } } globalState.trackingDerivation = prevTracking bindDependencies(derivation) @@ -158,24 +166,24 @@ function bindDependencies(derivation: IDerivation) { // 0: first occurrence, change to 1 and keep it // 1: extra occurrence, drop it let i0 = 0, - l = derivation.unboundDepsCount + l = derivation.unboundDepsCount for (let i = 0; i < l; i++) { - const dep = observing[i] + const dep = observing[i] if (dep.diffValue === 0) { - dep.diffValue = 1 + dep.diffValue = 1 if (i0 !== i) observing[i0] = dep - i0++ + i0++ } // Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined, // not hitting the condition if (((dep as any) as IDerivation).dependenciesState > lowestNewObservingDerivationState) { - lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState + lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState } } observing.length = i0 - derivation.newObserving = null // newObserving shouldn't be needed outside tracking (statement moved down to work around FF bug, see #614) + derivation.newObserving = null // newObserving shouldn't be needed outside tracking (statement moved down to work around FF bug, see #614) // Go through all old observables and check diffValue: (it is unique after last bindDependencies) // 0: it's not in new observables, unobserve it diff --git a/src/core/globalstate.ts b/src/core/globalstate.ts index d74190e979..59e98d9a27 100644 --- a/src/core/globalstate.ts +++ b/src/core/globalstate.ts @@ -84,6 +84,12 @@ export class MobXGlobals { * Globally attached error handlers that react specifically to errors in reactions */ globalReactionErrorHandlers: ((error: any, derivation: IDerivation) => void)[] = [] + + /** + * Don't catch and rethrow exceptions. This is useful for inspecting the state of + * the stack when an exception occurs while debugging. + */ + disableErrorBoundaries = false } export let globalState: MobXGlobals = new MobXGlobals() @@ -153,3 +159,19 @@ export function resetGlobalState() { if (persistentKeys.indexOf(key) === -1) globalState[key] = defaultGlobals[key] globalState.allowStateChanges = !globalState.strictMode } + +/** + * Don't catch and rethrow exceptions. This is useful for inspecting the state of + * the stack when an exception occurs while debugging. + */ +export function disableErrorBoundaries() { + console.warn("WARNING: Debug feature only. MobX will NOT recover from errors if this is on.") + globalState.disableErrorBoundaries = true +} + +/** + * Opposite of disableErrorBoundaries + */ +export function enableErrorBoundaries() { + globalState.disableErrorBoundaries = false +} \ No newline at end of file diff --git a/src/mobx.ts b/src/mobx.ts index 81f5d46952..aa9c5f702c 100644 --- a/src/mobx.ts +++ b/src/mobx.ts @@ -98,7 +98,9 @@ import { resetGlobalState, shareGlobalState, getGlobalState, - isolateGlobalState + isolateGlobalState, + disableErrorBoundaries, + enableErrorBoundaries } from "./core/globalstate" import { IDerivation } from "./core/derivation" import { IDepTreeNode } from "./core/observable" @@ -139,7 +141,9 @@ export const extras = { spyReport, spyReportEnd, spyReportStart, - setReactionScheduler + setReactionScheduler, + disableErrorBoundaries, + enableErrorBoundaries, } // Deprecated default export (will be removed in 4.0) @@ -260,8 +264,8 @@ for (let p in everything) { warnedAboutDefaultExport = true console.warn( "Using default export (`import mobx from 'mobx'`) is deprecated " + - "and won’t work in mobx@4.0.0\n" + - "Use `import * as mobx from 'mobx'` instead" + "and won’t work in mobx@4.0.0\n" + + "Use `import * as mobx from 'mobx'` instead" ) } return val