Skip to content

Commit 1673082

Browse files
committed
observable promise utilities
1 parent 22ecb93 commit 1673082

File tree

5 files changed

+144
-32
lines changed

5 files changed

+144
-32
lines changed

src/vs/base/common/observable.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,15 @@ export {
4545
observableFromPromise,
4646
observableSignal,
4747
observableSignalFromEvent,
48-
waitForState,
4948
wasEventTriggeredRecently,
5049
} from 'vs/base/common/observableInternal/utils';
50+
export {
51+
ObservableLazy,
52+
ObservableLazyStatefulPromise,
53+
ObservablePromise,
54+
PromiseResult,
55+
waitForState,
56+
} from 'vs/base/common/observableInternal/promise';
5157

5258
import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableInternal/logging';
5359

src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts renamed to src/vs/base/common/observableInternal/promise.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
5-
6-
import { IObservable, derived, observableValue } from 'vs/base/common/observable';
5+
import { autorun } from 'vs/base/common/observableInternal/autorun';
6+
import { IObservable, observableValue } from './base';
7+
import { derived } from 'vs/base/common/observableInternal/derived';
78

89
export class ObservableLazy<T> {
910
private readonly _value = observableValue<T | undefined>(this, undefined);
@@ -97,3 +98,44 @@ export class ObservableLazyStatefulPromise<T> {
9798
return this._lazyValue.getValue().promise;
9899
}
99100
}
101+
102+
/**
103+
* Resolves the promise when the observables state matches the predicate.
104+
*/
105+
export function waitForState<T, TState extends T>(observable: IObservable<T>, predicate: (state: T) => state is TState, isError?: (state: T) => boolean | unknown | undefined): Promise<TState>;
106+
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined): Promise<T>;
107+
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined): Promise<T> {
108+
return new Promise((resolve, reject) => {
109+
let isImmediateRun = true;
110+
let shouldDispose = false;
111+
const stateObs = observable.map(state => {
112+
/** @description waitForState.state */
113+
return {
114+
isFinished: predicate(state),
115+
error: isError ? isError(state) : false,
116+
state
117+
};
118+
});
119+
const d = autorun(reader => {
120+
/** @description waitForState */
121+
const { isFinished, error, state } = stateObs.read(reader);
122+
if (isFinished || error) {
123+
if (isImmediateRun) {
124+
// The variable `d` is not initialized yet
125+
shouldDispose = true;
126+
} else {
127+
d.dispose();
128+
}
129+
if (error) {
130+
reject(error === true ? state : error);
131+
} else {
132+
resolve(state);
133+
}
134+
}
135+
});
136+
isImmediateRun = false;
137+
if (shouldDispose) {
138+
d.dispose();
139+
}
140+
});
141+
}

src/vs/base/common/observableInternal/utils.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,6 @@ export function observableFromPromise<T>(promise: Promise<T>): IObservable<{ val
5151
return observable;
5252
}
5353

54-
export function waitForState<T, TState extends T>(observable: IObservable<T>, predicate: (state: T) => state is TState): Promise<TState>;
55-
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T>;
56-
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T> {
57-
return new Promise(resolve => {
58-
let didRun = false;
59-
let shouldDispose = false;
60-
const stateObs = observable.map(state => ({ isFinished: predicate(state), state }));
61-
const d = autorun(reader => {
62-
/** @description waitForState */
63-
const { isFinished, state } = stateObs.read(reader);
64-
if (isFinished) {
65-
if (!didRun) {
66-
shouldDispose = true;
67-
} else {
68-
d.dispose();
69-
}
70-
resolve(state);
71-
}
72-
});
73-
didRun = true;
74-
if (shouldDispose) {
75-
d.dispose();
76-
}
77-
});
78-
}
79-
8054
export function observableFromEvent<T, TArgs = unknown>(
8155
event: Event<TArgs>,
8256
getValue: (args: TArgs | undefined) => T

src/vs/base/test/common/observable.test.ts

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as assert from 'assert';
77
import { Emitter, Event } from 'vs/base/common/event';
8-
import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved } from 'vs/base/common/observable';
8+
import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved, waitForState } from 'vs/base/common/observable';
99
import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableInternal/base';
1010
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
1111

@@ -1103,6 +1103,97 @@ suite('observables', () => {
11031103
'myObservable.lastObserverRemoved',
11041104
]);
11051105
});
1106+
1107+
suite('waitForState', () => {
1108+
test('resolve', async () => {
1109+
const log = new Log();
1110+
const myObservable = new LoggingObservableValue('myObservable', { state: 'initializing' as 'initializing' | 'ready' | 'error' }, log);
1111+
1112+
const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => {
1113+
log.log(`resolved ${JSON.stringify(r)}`);
1114+
}, (err) => {
1115+
log.log(`rejected ${JSON.stringify(err)}`);
1116+
});
1117+
1118+
assert.deepStrictEqual(log.getAndClearEntries(), [
1119+
'myObservable.firstObserverAdded',
1120+
'myObservable.get',
1121+
]);
1122+
1123+
myObservable.set({ state: 'ready' }, undefined);
1124+
1125+
assert.deepStrictEqual(log.getAndClearEntries(), [
1126+
'myObservable.set (value [object Object])',
1127+
'myObservable.get',
1128+
'myObservable.lastObserverRemoved',
1129+
]);
1130+
1131+
await p;
1132+
1133+
assert.deepStrictEqual(log.getAndClearEntries(), [
1134+
'resolved {\"state\":\"ready\"}',
1135+
]);
1136+
});
1137+
1138+
test('resolveImmediate', async () => {
1139+
const log = new Log();
1140+
const myObservable = new LoggingObservableValue('myObservable', { state: 'ready' as 'initializing' | 'ready' | 'error' }, log);
1141+
1142+
const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => {
1143+
log.log(`resolved ${JSON.stringify(r)}`);
1144+
}, (err) => {
1145+
log.log(`rejected ${JSON.stringify(err)}`);
1146+
});
1147+
1148+
assert.deepStrictEqual(log.getAndClearEntries(), [
1149+
'myObservable.firstObserverAdded',
1150+
'myObservable.get',
1151+
'myObservable.lastObserverRemoved',
1152+
]);
1153+
1154+
myObservable.set({ state: 'error' }, undefined);
1155+
1156+
assert.deepStrictEqual(log.getAndClearEntries(), [
1157+
'myObservable.set (value [object Object])',
1158+
]);
1159+
1160+
await p;
1161+
1162+
assert.deepStrictEqual(log.getAndClearEntries(), [
1163+
'resolved {\"state\":\"ready\"}',
1164+
]);
1165+
});
1166+
1167+
test('reject', async () => {
1168+
const log = new Log();
1169+
const myObservable = new LoggingObservableValue('myObservable', { state: 'initializing' as 'initializing' | 'ready' | 'error' }, log);
1170+
1171+
const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => {
1172+
log.log(`resolved ${JSON.stringify(r)}`);
1173+
}, (err) => {
1174+
log.log(`rejected ${JSON.stringify(err)}`);
1175+
});
1176+
1177+
assert.deepStrictEqual(log.getAndClearEntries(), [
1178+
'myObservable.firstObserverAdded',
1179+
'myObservable.get',
1180+
]);
1181+
1182+
myObservable.set({ state: 'error' }, undefined);
1183+
1184+
assert.deepStrictEqual(log.getAndClearEntries(), [
1185+
'myObservable.set (value [object Object])',
1186+
'myObservable.get',
1187+
'myObservable.lastObserverRemoved',
1188+
]);
1189+
1190+
await p;
1191+
1192+
assert.deepStrictEqual(log.getAndClearEntries(), [
1193+
'rejected {\"state\":\"error\"}'
1194+
]);
1195+
});
1196+
});
11061197
});
11071198

11081199
export class LoggingObserver implements IObserver {

src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } fr
1111
import { parse } from 'vs/base/common/marshalling';
1212
import { Schemas } from 'vs/base/common/network';
1313
import { deepClone } from 'vs/base/common/objects';
14-
import { autorun, derived, observableFromEvent } from 'vs/base/common/observable';
14+
import { ObservableLazyStatefulPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable';
1515
import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils';
1616
import { ThemeIcon } from 'vs/base/common/themables';
1717
import { isDefined, isObject } from 'vs/base/common/types';
@@ -29,7 +29,6 @@ import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOpt
2929
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
3030
import { MultiDiffEditorIcon } from 'vs/workbench/contrib/multiDiffEditor/browser/icons.contribution';
3131
import { ConstResolvedMultiDiffSource, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService';
32-
import { ObservableLazyStatefulPromise } from 'vs/workbench/contrib/multiDiffEditor/browser/utils';
3332
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
3433
import { ILanguageSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
3534

0 commit comments

Comments
 (0)