Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 4725ccd

Browse files
Fix nested waitUntil (#606)
* Fix nested `waitUntil` not being awaited * Remove changeset * Move `waitUntilAll()` to `standards/event.ts` * Add nested `waitUntil` tests for `scheduled` and `queue` events * Add nested `waitUntil` tests for unit testing environments --------- Co-authored-by: bcoll <[email protected]>
1 parent 0ca8312 commit 4725ccd

File tree

5 files changed

+92
-5
lines changed

5 files changed

+92
-5
lines changed

packages/core/src/standards/event.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ const SUGGEST_GLOBAL_BINDING_MODULES =
3232
"handlers/Durable Object constructors, or `context.env` with " +
3333
"Pages Functions.";
3434

35+
// Like `Promise.all()`, but also handles nested changes to the promises array
36+
export async function waitUntilAll<WaitUntil extends any[] = unknown[]>(
37+
promises: Promise<unknown>[]
38+
): Promise<WaitUntil> {
39+
let len = 0;
40+
let last: WaitUntil = [] as unknown as WaitUntil;
41+
// When the length of the array changes, there has been a nested call to
42+
// `waitUntil` and we should await the promises again
43+
while (len !== promises.length) {
44+
len = promises.length;
45+
last = (await Promise.all(promises)) as WaitUntil;
46+
}
47+
return last;
48+
}
49+
3550
const kResponse = Symbol("kResponse");
3651
const kPassThrough = Symbol("kPassThrough");
3752
export const kWaitUntil = Symbol("kWaitUntil");
@@ -404,7 +419,7 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
404419
}
405420

406421
// noinspection ES6MissingAwait
407-
const waitUntil = Promise.all(event[kWaitUntil]) as Promise<WaitUntil>;
422+
const waitUntil = waitUntilAll<WaitUntil>(event[kWaitUntil]);
408423
return withWaitUntil(res, waitUntil);
409424
}
410425

@@ -436,7 +451,7 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
436451
}
437452

438453
// noinspection ES6MissingAwait
439-
const waitUntil = Promise.all(event[kWaitUntil]) as Promise<WaitUntil>;
454+
const waitUntil = waitUntilAll<WaitUntil>(event[kWaitUntil]);
440455
return withWaitUntil(await fetch(request), waitUntil);
441456
}
442457

@@ -449,15 +464,15 @@ export class ServiceWorkerGlobalScope extends WorkerGlobalScope {
449464
cron: cron ?? "",
450465
});
451466
super.dispatchEvent(event);
452-
return (await Promise.all(event[kWaitUntil])) as WaitUntil;
467+
return waitUntilAll<WaitUntil>(event[kWaitUntil]);
453468
}
454469

455470
async [kDispatchQueue]<WaitUntil extends any[] = any[]>(
456471
batch: MessageBatch
457472
): Promise<WaitUntil> {
458473
const event = new QueueEvent("queue", { batch });
459474
super.dispatchEvent(event);
460-
return (await Promise.all(event[kWaitUntil])) as WaitUntil;
475+
return waitUntilAll<WaitUntil>(event[kWaitUntil]);
461476
}
462477

463478
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types

packages/core/test/index.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,3 +1384,48 @@ test("MiniflareCore: dispose: cleans up watcher", async (t) => {
13841384
await setTimeout(100); // Shouldn't reload here
13851385
t.is(log.logsAtLevel(LogLevel.DEBUG).length, 0);
13861386
});
1387+
test("MiniflareCore: dispatchFetch: awaits nested waitUntil", async (t) => {
1388+
const mf = useMiniflare(
1389+
{},
1390+
{
1391+
script: `
1392+
async function waitAgain(ctx) {
1393+
await scheduler.wait(100);
1394+
ctx.waitUntil(scheduler.wait(100).then(() => 2));
1395+
return 1;
1396+
}
1397+
1398+
export default {
1399+
async fetch(req, env, ctx) {
1400+
ctx.waitUntil(waitAgain(ctx));
1401+
return new Response();
1402+
},
1403+
async scheduled(controller, env, ctx) {
1404+
ctx.waitUntil(waitAgain(ctx));
1405+
return 3;
1406+
},
1407+
async queue(batch, env, ctx) {
1408+
ctx.waitUntil(waitAgain(ctx));
1409+
return 4;
1410+
}
1411+
}
1412+
`,
1413+
modules: true,
1414+
},
1415+
log
1416+
);
1417+
1418+
const res = await mf.dispatchFetch("https://test");
1419+
let waitUntil = await res.waitUntil();
1420+
t.deepEqual(waitUntil, [1, 2]);
1421+
1422+
waitUntil = await mf.dispatchScheduled();
1423+
t.deepEqual(waitUntil, [1, 3, 2]);
1424+
1425+
waitUntil = await mf.dispatchQueue({
1426+
queue: "queue",
1427+
messages: [],
1428+
retryAll() {},
1429+
});
1430+
t.deepEqual(waitUntil, [1, 4, 2]);
1431+
});

packages/jest-environment-miniflare/test/fixtures/waitUntil.worker.spec.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,16 @@ test("ExecutionContext: waitUntil: await resolving promise array using getMinifl
7171
expect(cachedResponse).toBeTruthy();
7272
expect(await cachedResponse.text()).toBe("example cache");
7373
});
74+
75+
test("getMiniflareWaitUntil: supports nested wait until", async () => {
76+
const ctx = new ExecutionContext();
77+
ctx.waitUntil(
78+
(async () => {
79+
await scheduler.wait(100);
80+
ctx.waitUntil(scheduler.wait(100).then(() => 2));
81+
return 1;
82+
})()
83+
);
84+
const waitUntilList = await getMiniflareWaitUntil(ctx);
85+
expect(waitUntilList).toEqual([1, 2]);
86+
});

packages/shared-test-environment/src/globals.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
MiniflareCore,
44
ScheduledEvent,
55
kWaitUntil,
6+
waitUntilAll,
67
} from "@miniflare/core";
78
import {
89
DurableObject,
@@ -88,7 +89,7 @@ export async function createMiniflareEnvironmentUtilities(
8889
getMiniflareWaitUntil<WaitUntil extends any[] = unknown[]>(
8990
event: FetchEvent | ScheduledEvent | ExecutionContext
9091
): Promise<WaitUntil> {
91-
return Promise.all(event[kWaitUntil]) as Promise<WaitUntil>;
92+
return waitUntilAll<WaitUntil>(event[kWaitUntil]);
9293
},
9394
async flushMiniflareDurableObjectAlarms(ids?: DurableObjectId[]) {
9495
const plugin = (await mf.getPlugins()).DurableObjectsPlugin;

packages/vitest-environment-miniflare/test/fixtures/service-worker/waitUntil.worker.spec.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,16 @@ test("ExecutionContext: waitUntil: await resolving promise array using getMinifl
7474
expect(cachedResponse).toBeTruthy();
7575
expect(await cachedResponse.text()).toBe("example cache");
7676
});
77+
78+
test("getMiniflareWaitUntil: supports nested wait until", async () => {
79+
const ctx = new ExecutionContext();
80+
ctx.waitUntil(
81+
(async () => {
82+
await scheduler.wait(100);
83+
ctx.waitUntil(scheduler.wait(100).then(() => 2));
84+
return 1;
85+
})()
86+
);
87+
const waitUntilList = await getMiniflareWaitUntil(ctx);
88+
expect(waitUntilList).toEqual([1, 2]);
89+
});

0 commit comments

Comments
 (0)