diff --git a/common/changes/@itwin/core-frontend/MathieuM-asyncWorker_2024-08-15-19-04.json b/common/changes/@itwin/core-frontend/MathieuM-asyncWorker_2024-08-15-19-04.json new file mode 100644 index 000000000000..ac11a63efe7e --- /dev/null +++ b/common/changes/@itwin/core-frontend/MathieuM-asyncWorker_2024-08-15-19-04.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-frontend", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/core-frontend" +} \ No newline at end of file diff --git a/core/frontend/src/test/worker/WorkerProxy.test.ts b/core/frontend/src/test/worker/WorkerProxy.test.ts index 025107cfbaaa..3167e6a1f604 100644 --- a/core/frontend/src/test/worker/WorkerProxy.test.ts +++ b/core/frontend/src/test/worker/WorkerProxy.test.ts @@ -45,4 +45,29 @@ describe("WorkerProxy", () => { worker.terminate(); }); + + it("returns results out of sequence if caller does not await each operation", async () => { + const worker = createWorker(); + + const [slowest, slow, fast] = await Promise.all([ + worker.someVeryLongRunningAsyncOperation(), + worker.someLongRunningAsyncOperation(), + worker.someFastSynchronousOperation(), + ]); + + expect(fast).to.be.lessThan(slow); + expect(slow).to.be.lessThan(slowest); + worker.terminate(); + }); + + it("returns results in sequence if caller awaits each operation", async () => { + const worker = createWorker(); + + const first = await worker.someVeryLongRunningAsyncOperation(); + const second = await worker.someLongRunningAsyncOperation(); + const third = await worker.someFastSynchronousOperation(); + + expect(first).to.be.lessThan(second); + expect(second).to.be.lessThan(third); + }); }); diff --git a/core/frontend/src/test/worker/test-worker.ts b/core/frontend/src/test/worker/test-worker.ts index 28594121e818..dae74db53413 100644 --- a/core/frontend/src/test/worker/test-worker.ts +++ b/core/frontend/src/test/worker/test-worker.ts @@ -24,6 +24,9 @@ export interface TestWorker { throwString(): never; setTransfer(wantTransfer: boolean): undefined; createGraphic(context: WorkerGraphicDescriptionContextProps): WorkerGraphic; + someVeryLongRunningAsyncOperation(): Promise; + someLongRunningAsyncOperation(): Promise; + someFastSynchronousOperation(): number; } let doTransfer = false; @@ -35,6 +38,15 @@ function maybeTransfer(result: T): T | { result: T, transfer: Transferable[] return { result, transfer: [] }; } +let globalTickCounter = 0; + +async function waitNTicks(nTicks: number): Promise { + let counter = 0; + while (++counter < nTicks) { + await new Promise((resolve: any) => setTimeout(resolve, 1)); + } +} + registerWorker({ zero: () => maybeTransfer("zero"), one: (arg: string) => maybeTransfer(arg), @@ -85,4 +97,16 @@ registerWorker({ transfer: Array.from(transferables), }; }, + + someVeryLongRunningAsyncOperation: async () => { + await waitNTicks(10); + return ++globalTickCounter; + }, + someLongRunningAsyncOperation: async () => { + await waitNTicks(5); + return ++globalTickCounter; + }, + someFastSynchronousOperation: () => { + return ++globalTickCounter; + }, }); diff --git a/core/frontend/src/workers/RegisterWorker.ts b/core/frontend/src/workers/RegisterWorker.ts index 8b583d89ab1e..354bb2319ccb 100644 --- a/core/frontend/src/workers/RegisterWorker.ts +++ b/core/frontend/src/workers/RegisterWorker.ts @@ -23,14 +23,17 @@ interface WorkerRequest { * @beta */ export function registerWorker(impl: WorkerImplementation): void { - onmessage = (e: MessageEvent) => { + onmessage = async (e: MessageEvent) => { const req = e.data as WorkerRequest; const msgId = req.msgId; try { assert(typeof req === "object" && "operation" in req && "payload" in req && "msgId" in req); const func = (impl as any)[req.operation]; assert(typeof func === "function"); - const ret = func(req.payload); + let ret = func(req.payload); + if (ret instanceof Promise) { + ret = await ret; + } if (typeof ret === "object" && "transfer" in ret) postMessage({ result: ret.result, msgId }, { transfer: ret.transfer }); else