Skip to content

Commit 10404f7

Browse files
authored
Add defer functionality to generator executor (#64157)
1 parent ee72eb0 commit 10404f7

4 files changed

Lines changed: 426 additions & 92 deletions

File tree

packages/typescript/src/api/async/api.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ export interface TranspileOutput {
221221
}
222222

223223
// @sync-only-start
224-
// export { all } from "./generatorSupport.ts";
225-
// import {all, type ExecutedGeneratorsResults, type APIRequestGenerator} from "./generatorSupport.ts";
224+
// export { all, defer, type APIRequestGenerator, type AnyAPIRequestGenerator, type DeferredAPIRequestGenerator, type ExecutedGeneratorsResults } from "./generatorSupport.ts";
225+
// import {executeRequestGenerators, type ExecutedGeneratorsResults, type AnyAPIRequestGenerator} from "./generatorSupport.ts";
226226
// @sync-only-end
227227

228228
export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHost {
@@ -259,13 +259,8 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
259259
}
260260
// @sync-skip-block-end
261261
// @sync-only-start
262-
// batch<T extends readonly APIRequestGenerator[]>(...requestGenerators: T): ExecutedGeneratorsResults<T> {
263-
// const batches = all(...requestGenerators);
264-
// let state = batches.next();
265-
// while (!state.done) {
266-
// state = batches.next(this.client.batchRequests(state.value).responses);
267-
// }
268-
// return state.value;
262+
// batch<T extends readonly AnyAPIRequestGenerator[]>(...requestGenerators: T): ExecutedGeneratorsResults<T> {
263+
// return executeRequestGenerators(requestGenerators, requests => this.client.batchRequests(requests).responses);
269264
// }
270265
// @sync-only-end
271266

packages/typescript/src/api/sync/api.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,11 @@ export interface TranspileOutput {
237237
sourceMapText?: string;
238238
}
239239

240-
export { all } from "./generatorSupport.ts";
240+
export { all, type AnyAPIRequestGenerator, type APIRequestGenerator, defer, type DeferredAPIRequestGenerator, type ExecutedGeneratorsResults } from "./generatorSupport.ts";
241241
import {
242-
all,
243-
type APIRequestGenerator,
242+
type AnyAPIRequestGenerator,
244243
type ExecutedGeneratorsResults,
244+
executeRequestGenerators,
245245
} from "./generatorSupport.ts";
246246

247247
export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHost {
@@ -287,13 +287,8 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
287287
);
288288
}
289289

290-
batch<T extends readonly APIRequestGenerator[]>(...requestGenerators: T): ExecutedGeneratorsResults<T> {
291-
const batches = all(...requestGenerators);
292-
let state = batches.next();
293-
while (!state.done) {
294-
state = batches.next(this.client.batchRequests(state.value).responses);
295-
}
296-
return state.value;
290+
batch<T extends readonly AnyAPIRequestGenerator[]>(...requestGenerators: T): ExecutedGeneratorsResults<T> {
291+
return executeRequestGenerators(requestGenerators, requests => this.client.batchRequests(requests).responses);
297292
}
298293

299294
private get ensureInitialized(): {

packages/typescript/src/api/sync/generatorSupport.ts

Lines changed: 128 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
APIResponse,
55
} from "../proto.ts";
66

7-
export function cacheGeneratorMethod<Sync extends (...args: any[]) => any, Gen extends (...args: any[]) => APIRequestGenerator>(
7+
export function cacheGeneratorMethod<Sync extends (...args: any[]) => any, Gen extends (...args: any[]) => AnyAPIRequestGenerator>(
88
owner: object,
99
name: PropertyKey,
1010
sync: Sync,
@@ -22,82 +22,144 @@ export function apiRequest<Method extends keyof APIMethodInfo>(
2222
export function* apiRequest(method: PropertyKey, params: unknown): Generator<{ method: PropertyKey; params: unknown; }, unknown, unknown> {
2323
return yield { method, params };
2424
}
25-
export type APIRequestGenerator<Return = any> = Generator<APIRequest | readonly APIRequest[], Return, any>;
25+
const deferredGeneratorMarker: unique symbol = Symbol();
26+
interface DeferredAPIRequest {
27+
readonly deferred: APIRequestGenerator;
28+
}
29+
type APIRequestGeneratorYield = APIRequest | readonly APIRequest[] | DeferredAPIRequest;
30+
export type APIRequestGenerator<Return = any> = Generator<APIRequestGeneratorYield, Return, any>;
31+
export type DeferredAPIRequestGenerator = Generator<DeferredAPIRequest, void, unknown> & { readonly [deferredGeneratorMarker]: true; };
32+
export type AnyAPIRequestGenerator<Return = any> = APIRequestGenerator<Return> | DeferredAPIRequestGenerator;
2633
type GeneratorReturn<T> = T extends Generator<any, infer R, any> ? R : never;
27-
export type ExecutedGeneratorsResults<T extends readonly APIRequestGenerator[]> = {
28-
[K in keyof T]: GeneratorReturn<T[K]>;
29-
};
34+
export type ExecutedGeneratorsResults<T extends readonly AnyAPIRequestGenerator[]> = number extends T["length"] ? GeneratorReturn<Exclude<T[number], DeferredAPIRequestGenerator>>[]
35+
: T extends readonly [infer Head extends AnyAPIRequestGenerator, ...infer Tail extends readonly AnyAPIRequestGenerator[]] ? Head extends DeferredAPIRequestGenerator ? ExecutedGeneratorsResults<Tail> : [GeneratorReturn<Head>, ...ExecutedGeneratorsResults<Tail>]
36+
: [];
3037

3138
interface GeneratorResponse {
3239
result: unknown;
3340
error?: string | undefined;
3441
}
3542

36-
export function all<const T extends readonly APIRequestGenerator[]>(
37-
...requestGenerators: T
38-
): Generator<APIRequest[], ExecutedGeneratorsResults<T>, readonly GeneratorResponse[]>;
39-
export function* all<T extends readonly APIRequestGenerator[]>(
40-
...requestGenerators: T
41-
): Generator<APIRequest[], ExecutedGeneratorsResults<T>, readonly GeneratorResponse[]> {
42-
const results: any[] = [];
43-
const requestObjects: (APIRequest | readonly APIRequest[] | undefined)[] = [];
44-
const completedIndices = new Set<number>();
45-
for (let i = 0; i < requestGenerators.length; i++) {
46-
const state = requestGenerators[i].next();
43+
interface RequestRunnerOptions {
44+
executeDeferred?: boolean;
45+
}
46+
47+
function createRequestRunner<T extends readonly AnyAPIRequestGenerator[]>(requestGenerators: T, options: RequestRunnerOptions = {}) {
48+
const registeredGenerators = new Set<AnyAPIRequestGenerator>();
49+
const requestsByGenerator = new Map<AnyAPIRequestGenerator, APIRequestGeneratorYield>();
50+
const resultsByGenerator = new Map<AnyAPIRequestGenerator, unknown>();
51+
for (const generator of requestGenerators) {
52+
addGenerator(generator);
53+
}
54+
const requestRounds = runRequestRounds();
55+
return { requestRounds, getResults };
56+
57+
function advanceGenerator(generator: AnyAPIRequestGenerator, value?: unknown, error?: string): void {
58+
let state = error === undefined
59+
? generator.next(value)
60+
: generator.throw(new Error(error));
61+
while (!state.done && isDeferredAPIRequest(state.value) && options.executeDeferred) {
62+
addGenerator(state.value.deferred);
63+
state = generator.next();
64+
}
4765
if (state.done) {
48-
results[i] = state.value;
49-
completedIndices.add(i);
66+
requestsByGenerator.delete(generator);
67+
resultsByGenerator.set(generator, state.value);
5068
}
5169
else {
52-
requestObjects[i] = state.value;
70+
requestsByGenerator.set(generator, state.value);
5371
}
5472
}
55-
while (completedIndices.size < requestGenerators.length) {
56-
const requests: APIRequest[] = [];
57-
const responseIndices: (number | readonly number[] | undefined)[] = [];
58-
const responseIndexByDeduplicationKey = new Map<string, number>();
59-
const addRequest = (request: APIRequest): number => {
60-
const deduplicationKey = getRequestDeduplicationKey(request);
61-
let responseIndex = deduplicationKey === undefined ? undefined : responseIndexByDeduplicationKey.get(deduplicationKey);
62-
if (responseIndex === undefined) {
63-
responseIndex = requests.length;
64-
requests.push(request);
65-
if (deduplicationKey !== undefined) responseIndexByDeduplicationKey.set(deduplicationKey, responseIndex);
66-
}
67-
return responseIndex;
68-
};
69-
for (let i = 0; i < requestGenerators.length; i++) {
70-
if (completedIndices.has(i)) continue;
7173

72-
const request = requestObjects[i]!;
73-
responseIndices[i] = isRequestGroup(request) ? request.map(addRequest) : addRequest(request);
74-
}
74+
function addGenerator(generator: AnyAPIRequestGenerator): void {
75+
if (registeredGenerators.has(generator)) throw new Error("Cannot execute the same generator instance more than once");
76+
registeredGenerators.add(generator);
77+
advanceGenerator(generator);
78+
}
7579

76-
const responses = yield requests;
77-
for (let i = 0; i < requestGenerators.length; i++) {
78-
if (completedIndices.has(i)) continue;
79-
80-
const requestGenerator = requestGenerators[i];
81-
const responseIndex = responseIndices[i]!;
82-
let state: IteratorResult<APIRequest | readonly APIRequest[], any>;
83-
if (typeof responseIndex === "number") {
84-
const result = responses[responseIndex];
85-
state = result.error
86-
? requestGenerator.throw(new Error(result.error))
87-
: requestGenerator.next(result.result);
80+
function* runRequestRounds(): Generator<APIRequest[] | DeferredAPIRequest, void, readonly GeneratorResponse[]> {
81+
while (requestsByGenerator.size) {
82+
for (const generator of registeredGenerators) {
83+
let request = requestsByGenerator.get(generator);
84+
while (request && isDeferredAPIRequest(request)) {
85+
yield request;
86+
advanceGenerator(generator);
87+
request = requestsByGenerator.get(generator);
88+
}
8889
}
89-
else {
90-
state = requestGenerator.next(responseIndex.map(index => responses[index]));
90+
if (!requestsByGenerator.size) break;
91+
92+
const requests: APIRequest[] = [];
93+
const responseIndexByDeduplicationKey = new Map<string, number>();
94+
const addRequest = (request: APIRequest): number => {
95+
const deduplicationKey = getRequestDeduplicationKey(request);
96+
let responseIndex = deduplicationKey === undefined ? undefined : responseIndexByDeduplicationKey.get(deduplicationKey);
97+
if (responseIndex === undefined) {
98+
responseIndex = requests.length;
99+
requests.push(request);
100+
if (deduplicationKey !== undefined) responseIndexByDeduplicationKey.set(deduplicationKey, responseIndex);
101+
}
102+
return responseIndex;
103+
};
104+
// TODO: Use Iterator.prototype.filter when target >= ES2025
105+
const roundGenerators = [...registeredGenerators].filter(generator => requestsByGenerator.has(generator));
106+
const responseIndices = new Map<AnyAPIRequestGenerator, number | readonly number[]>();
107+
for (const generator of roundGenerators) {
108+
const request = requestsByGenerator.get(generator) as APIRequest | readonly APIRequest[];
109+
responseIndices.set(generator, isRequestGroup(request) ? request.map(addRequest) : addRequest(request));
91110
}
92-
if (state.done) {
93-
results[i] = state.value;
94-
completedIndices.add(i);
95-
requestObjects[i] = undefined;
111+
112+
const responses = yield requests;
113+
for (const generator of roundGenerators) {
114+
const responseIndex = responseIndices.get(generator)!;
115+
if (typeof responseIndex === "number") {
116+
const result = responses[responseIndex];
117+
advanceGenerator(generator, result.result, result.error || undefined);
118+
}
119+
else {
120+
advanceGenerator(generator, responseIndex.map(index => responses[index]));
121+
}
96122
}
97-
else requestObjects[i] = state.value;
98123
}
99124
}
100-
return results as any[] as ExecutedGeneratorsResults<T>;
125+
126+
function getResults(): ExecutedGeneratorsResults<T> {
127+
return requestGenerators
128+
.filter(generator => !isDeferredGenerator(generator))
129+
.map(generator => resultsByGenerator.get(generator)) as ExecutedGeneratorsResults<T>;
130+
}
131+
}
132+
133+
export function all<const T extends readonly AnyAPIRequestGenerator[]>(
134+
...requestGenerators: T
135+
): APIRequestGenerator<ExecutedGeneratorsResults<T>>;
136+
export function* all<T extends readonly AnyAPIRequestGenerator[]>(
137+
...requestGenerators: T
138+
): APIRequestGenerator<ExecutedGeneratorsResults<T>> {
139+
const { requestRounds, getResults } = createRequestRunner(requestGenerators);
140+
yield* requestRounds;
141+
return getResults();
142+
}
143+
144+
export function executeRequestGenerators<T extends readonly AnyAPIRequestGenerator[]>(
145+
requestGenerators: T,
146+
executeRequests: (requests: APIRequest[]) => readonly GeneratorResponse[],
147+
): ExecutedGeneratorsResults<T> {
148+
const { requestRounds, getResults } = createRequestRunner(requestGenerators, { executeDeferred: true });
149+
let state = requestRounds.next();
150+
while (!state.done) {
151+
if (isDeferredAPIRequest(state.value)) throw new Error("Unexpected deferred request");
152+
state = requestRounds.next(executeRequests(state.value));
153+
}
154+
return getResults();
155+
}
156+
157+
function isDeferredAPIRequest(request: APIRequestGeneratorYield): request is DeferredAPIRequest {
158+
return !Array.isArray(request) && "deferred" in request;
159+
}
160+
161+
function isDeferredGenerator(generator: AnyAPIRequestGenerator): generator is DeferredAPIRequestGenerator {
162+
return deferredGeneratorMarker in generator;
101163
}
102164

103165
function isRequestGroup(request: APIRequest | readonly APIRequest[]): request is readonly APIRequest[] {
@@ -112,3 +174,11 @@ function getRequestDeduplicationKey(request: APIRequest): string | undefined {
112174
return undefined;
113175
}
114176
}
177+
178+
export function defer(gen: APIRequestGenerator): DeferredAPIRequestGenerator {
179+
const deferred = (function* (): Generator<DeferredAPIRequest, void, unknown> {
180+
yield { deferred: gen };
181+
})() as DeferredAPIRequestGenerator;
182+
Object.defineProperty(deferred, deferredGeneratorMarker, { value: true });
183+
return deferred;
184+
}

0 commit comments

Comments
 (0)