Last active
November 10, 2022 13:34
-
-
Save RomkeVdMeulen/f774324202d3fb8b710e7e2b1dcfdeb0 to your computer and use it in GitHub Desktop.
@queuedOperation TypeScript method decorator: http://romkevandermeulen.nl/2018/01/27/queued-operation-decorator.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface OperationQueue { | |
queue: Promise<void>; | |
} | |
export function makeOperationQueue(): OperationQueue { | |
return {queue: Promise.resolve()}; | |
} | |
export function queuedOperation(operationQueue: OperationQueue) { | |
return function(_target: any, _key: string, descriptor: TypedPropertyDescriptor<(...args: any[]) => any>) { | |
const method = descriptor.value!; | |
descriptor.value = function(...args: any[]) { | |
const operation = operationQueue.queue.then(() => { | |
return method.apply(this, args); | |
}); | |
operationQueue.queue = operation.catch(() => {}); | |
return operation; | |
}; | |
return descriptor; | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe("makeOperationQueue() / @queuedOperation", () => { | |
it("puts calls to async methods in a queue", async () => { | |
const operationQueue = makeOperationQueue(); | |
class QueuedOperations { | |
started: Array<{name: string, resolve: () => void}> = []; | |
get startedMethods() { | |
return this.started.map(o => o.name); | |
} | |
waitForNumberStarted(number: number) { | |
return new Promise(resolve => { | |
const check = () => { | |
if (this.started.length === number) { | |
resolve(); | |
} else { | |
setTimeout(() => check(), 1); | |
} | |
}; | |
check(); | |
}); | |
} | |
@queuedOperation(operationQueue) | |
queuedOne() { | |
return new Promise(res => { | |
this.started.push({name: "one", resolve: res}); | |
}); | |
} | |
@queuedOperation(operationQueue) | |
queuedTwo() { | |
return new Promise(res => { | |
this.started.push({name: "two", resolve: res}); | |
}); | |
} | |
unqueued() { | |
return new Promise(res => { | |
this.started.push({name: "unqueued", resolve: res}); | |
}); | |
} | |
} | |
const instance = new QueuedOperations(); | |
expect(instance.startedMethods).to.be.empty; | |
instance.queuedOne(); | |
await instance.waitForNumberStarted(1); | |
expect(instance.startedMethods).to.deep.equal(["one"]); | |
instance.queuedTwo(); | |
instance.queuedOne(); | |
instance.unqueued(); | |
expect(instance.startedMethods).to.deep.equal(["one", "unqueued"]); | |
instance.started[0].resolve(); | |
await instance.waitForNumberStarted(3); | |
expect(instance.startedMethods).to.deep.equal(["one", "unqueued", "two"]); | |
instance.started[2].resolve(); | |
await instance.waitForNumberStarted(4); | |
expect(instance.startedMethods).to.deep.equal(["one", "unqueued", "two", "one"]); | |
}); | |
it("doesn't interupt the queue if a method returns a rejected promise", async () => { | |
const operationQueue = makeOperationQueue(); | |
class QueuedWithErrors { | |
started: string[] = []; | |
@queuedOperation(operationQueue) | |
async noError() { | |
this.started.push("noError"); | |
} | |
@queuedOperation(operationQueue) | |
async withError() { | |
this.started.push("withError"); | |
throw new Error("Error during operation"); | |
} | |
waitForNumberStarted(number: number) { | |
return new Promise(resolve => { | |
const check = () => { | |
if (this.started.length === number) { | |
resolve(); | |
} else { | |
setTimeout(() => check(), 1); | |
} | |
}; | |
check(); | |
}); | |
} | |
} | |
const instance = new QueuedWithErrors(); | |
instance.withError().catch(() => {}); // prevents "unhandled rejection Error" | |
instance.noError(); | |
await instance.waitForNumberStarted(2); | |
expect(instance.started).to.deep.equal(["withError", "noError"]); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment