ObservablePromise cleanup

This commit is contained in:
Henning Dieterichs
2024-02-16 13:05:04 +01:00
committed by Henning Dieterichs
parent e713ba0fb9
commit bc7e1fc609
4 changed files with 70 additions and 20 deletions
+7 -2
View File
@@ -49,15 +49,20 @@ export {
} from 'vs/base/common/observableInternal/utils';
export {
ObservableLazy,
ObservableLazyStatefulPromise,
ObservableLazyPromise,
ObservablePromise,
PromiseResult,
waitForState,
derivedWithCancellationToken,
} from 'vs/base/common/observableInternal/promise';
import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableInternal/logging';
const enableLogging = false;
// Remove "//" in the next line to enable logging
const enableLogging = false
// || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this
;
if (enableLogging) {
setLogger(new ConsoleObservableLogger());
}
@@ -10,7 +10,7 @@ import { DebugNameData, IDebugNameData, Owner } from 'vs/base/common/observableI
import { getLogger } from 'vs/base/common/observableInternal/logging';
export type EqualityComparer<T> = (a: T, b: T) => boolean;
const defaultEqualityComparer: EqualityComparer<any> = (a, b) => a === b;
export const defaultEqualityComparer: EqualityComparer<any> = (a, b) => a === b;
/**
* Creates an observable that is derived from other observables.
@@ -3,8 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { autorun } from 'vs/base/common/observableInternal/autorun';
import { IObservable, observableValue } from './base';
import { derived } from 'vs/base/common/observableInternal/derived';
import { IObservable, IReader, observableValue, transaction } from './base';
import { Derived, defaultEqualityComparer, derived } from 'vs/base/common/observableInternal/derived';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { DebugNameData, Owner } from 'vs/base/common/observableInternal/debugName';
export class ObservableLazy<T> {
private readonly _value = observableValue<T | undefined>(this, undefined);
@@ -38,15 +40,29 @@ export class ObservableLazy<T> {
export class ObservablePromise<T> {
private readonly _value = observableValue<PromiseResult<T> | undefined>(this, undefined);
/**
* The promise that this object wraps.
*/
public readonly promise: Promise<T>;
public readonly value: IObservable<PromiseResult<T> | undefined> = this._value;
/**
* The current state of the promise.
* Is `undefined` if the promise didn't resolve yet.
*/
public readonly promiseResult: IObservable<PromiseResult<T> | undefined> = this._value;
constructor(promise: Promise<T>) {
this.promise = promise.then(value => {
this._value.set(new PromiseResult(value, undefined), undefined);
transaction(tx => {
/** @description onPromiseResolved */
this._value.set(new PromiseResult(value, undefined), tx);
});
return value;
}, error => {
this._value.set(new PromiseResult<T>(undefined, error), undefined);
transaction(tx => {
/** @description onPromiseRejected */
this._value.set(new PromiseResult<T>(undefined, error), tx);
});
throw error;
});
}
@@ -58,7 +74,7 @@ export class PromiseResult<T> {
* The value of the resolved promise.
* Undefined if the promise rejected.
*/
public readonly value: T | undefined,
public readonly data: T | undefined,
/**
* The error in case of a rejected promise.
@@ -71,30 +87,30 @@ export class PromiseResult<T> {
/**
* Returns the value if the promise resolved, otherwise throws the error.
*/
public getValue(): T {
public getDataOrThrow(): T {
if (this.error) {
throw this.error;
}
return this.value!;
return this.data!;
}
}
/**
* A lazy promise whose state is observable.
*/
export class ObservableLazyStatefulPromise<T> {
private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computeValue()));
export class ObservableLazyPromise<T> {
private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computePromise()));
/**
* Does not enforce evaluation of the promise compute function.
* Is undefined if the promise has not been computed yet.
*/
public readonly cachedValue = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.value.read(reader));
public readonly cachedPromiseResult = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.promiseResult.read(reader));
constructor(private readonly _computeValue: () => Promise<T>) {
constructor(private readonly _computePromise: () => Promise<T>) {
}
public getValue(): Promise<T> {
public getPromise(): Promise<T> {
return this._lazyValue.getValue().promise;
}
}
@@ -139,3 +155,32 @@ export function waitForState<T>(observable: IObservable<T>, predicate: (state: T
}
});
}
export function derivedWithCancellationToken<T>(computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable<T>;
export function derivedWithCancellationToken<T>(owner: object, computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable<T>;
export function derivedWithCancellationToken<T>(computeFnOrOwner: ((reader: IReader, cancellationToken: CancellationToken) => T) | object, computeFnOrUndefined?: ((reader: IReader, cancellationToken: CancellationToken) => T)): IObservable<T> {
let computeFn: (reader: IReader, store: CancellationToken) => T;
let owner: Owner;
if (computeFnOrUndefined === undefined) {
computeFn = computeFnOrOwner as any;
owner = undefined;
} else {
owner = computeFnOrOwner;
computeFn = computeFnOrUndefined as any;
}
let cancellationTokenSource: CancellationTokenSource | undefined = undefined;
return new Derived(
new DebugNameData(owner, undefined, computeFn),
r => {
if (cancellationTokenSource) {
cancellationTokenSource.dispose(true);
}
cancellationTokenSource = new CancellationTokenSource();
return computeFn(r, cancellationTokenSource.token);
}, undefined,
undefined,
() => cancellationTokenSource?.dispose(),
defaultEqualityComparer,
);
}
@@ -11,7 +11,7 @@ import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } fr
import { parse } from 'vs/base/common/marshalling';
import { Schemas } from 'vs/base/common/network';
import { deepClone } from 'vs/base/common/objects';
import { ObservableLazyStatefulPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable';
import { ObservableLazyPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable';
import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils';
import { ThemeIcon } from 'vs/base/common/themables';
import { isDefined, isObject } from 'vs/base/common/types';
@@ -131,7 +131,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor
});
private async _createModel(): Promise<IMultiDiffEditorModel & IDisposable> {
const source = await this._resolvedSource.getValue();
const source = await this._resolvedSource.getPromise();
const textResourceConfigurationService = this._textResourceConfigurationService;
// Enables delayed disposing
@@ -207,7 +207,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor
};
}
private readonly _resolvedSource = new ObservableLazyStatefulPromise(async () => {
private readonly _resolvedSource = new ObservableLazyPromise(async () => {
const source: IResolvedMultiDiffSource | undefined = this.initialResources
? new ConstResolvedMultiDiffSource(this.initialResources)
: await this._multiDiffSourceResolverService.resolve(this.multiDiffSource);
@@ -229,7 +229,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor
return false;
}
private readonly _resources = derived(this, reader => this._resolvedSource.cachedValue.read(reader)?.value?.resources.read(reader));
private readonly _resources = derived(this, reader => this._resolvedSource.cachedPromiseResult.read(reader)?.data?.resources.read(reader));
private readonly _isDirtyObservables = mapObservableArrayCached(this, this._resources.map(r => r || []), res => {
const isModifiedDirty = res.modified ? isUriDirty(this._textFileService, res.modified) : constObservable(false);
const isOriginalDirty = res.original ? isUriDirty(this._textFileService, res.original) : constObservable(false);