Skip to content

Instantly share code, notes, and snippets.

@felipecrv
Created April 25, 2024 23:00
Show Gist options
  • Save felipecrv/b7084c0fa528916c0e1cc0e1732c9b7a to your computer and use it in GitHub Desktop.
Save felipecrv/b7084c0fa528916c0e1cc0e1732c9b7a to your computer and use it in GitHub Desktop.
Concurrency Control for shared mutable state that gets mutated by asynchronous callbacks.
/* @flow */
export class CancellationToken {
source: CancellationTokenSource;
requestId: number;
constructor(source: CancellationTokenSource, requestId: number) {
this.source = source;
this.requestId = requestId;
}
isCancelled(): boolean {
return this.source.requestId != this.requestId;
}
}
export default class CancellationTokenSource {
requestId: number;
constructor() {
this.requestId = 0;
}
cancel(): void {
this.requestId++;
}
token(): CancellationToken {
return new CancellationToken(this, this.requestId);
}
}
export class StorageCancellationToken {
source: StorageCancellationTokenSource;
requestId: number;
constructor(source: StorageCancellationTokenSource, requestId: number) {
this.source = source;
this.requestId = requestId;
}
isCancelled(): boolean {
return this.source._loadRequestId() != this.requestId;
}
}
export class StorageCancellationTokenSource {
storageKey: string;
constructor(key: string) {
this.storageKey = key;
}
_loadRequestId() {
const item = localStorage.getItem(this.storageKey) || '0';
return parseInt(item, 10);
}
cancel(): void {
const newRequestId = this._loadRequestId() + 1;
localStorage.setItem(this.storageKey, '' + newRequestId);
}
token(): StorageCancellationToken {
const requestId = this._loadRequestId();
return new StorageCancellationToken(this, requestId);
}
}
@felipecrv
Copy link
Author

Example of usage on a screen like https://twitter.com/geoffreylitt/status/1783566102838104313

// CTS protecting the input and output=2*input against conflicting updates
const cts = new CancellationTokenSource();

function onClick(oldNumber) {
  cts.cancel();
  const token = cts.token;

  const number = oldNumber + 1;
  updateInput(number);
  updateOutput('calculating...');
  setTimeout(() => {
    if (token.isCancelled()) return;  // avoid starting work that will be wasted

    // "expensive" computation on number that happens asynchronously
    setTimeout(() => {
      if (token.isCancelled()) return;  // avoid commiting inconsistent state

      updateOutput(number * 2);
    }, /*delay=*/Math.floor(Math.random() * 1001));
  }, /*delay=*/Math.floor(Math.random() * 1001));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment