Skip to content

Cancellation Samples

Ron Buckton edited this page Apr 2, 2015 · 3 revisions
// if canceled, leaves Promise in a forever pending state.
function delay(ms: number, token = CancellationToken.none) {
  return new Promise<void>(resolve => {
    let reg = token.register(() => { clearTimeout(handle); });
    let handle = setTimeout(() => { reg.unregister(); resolve(); }, ms);
  });
}

// if canceled, rejects Promise with the provided cancellation reason.
function fetch(url, token = CancellationToken.none) {
  return new Promise<string>((resolve, reject) => {
    let reg = token.register((reason) => { xhr.abort(); reject(reason); });
    let xhr = new XMLHttpRequest();
    xhr.onload = () => { reg.unregister(); resolve(xhr.responseText); };
    xhr.onerror = () => { reg.unregister(); reject(xhr.statusText); };
    xhr.open("GET", url);
    xhr.send(null);
  });
}

// async loop, cancellation ends the loop
function loop(callback, token = CancellationToken.none) {
  const step = () => {
    if (token.canceled) {
      return Promise.resolve();
    }
    return Promise.resolve(callback()).then(step);
  }
  return step();
}

// subordinate cancellation - root can cancel a graph
class DownloadManager {
  constructor() {
    // root cts
    this.cts = new CancellationTokenSource();
  }

  download(url, token = CancellationToken.none) {
    // create a source linked to the provided token and the root
    let linked = new CancellationTokenSource(this.cts.token, token);
    return fetch(url, linked.token);
  }  

  close() {
    // cancel all pending fetches.
    this.cts.cancel();
  }
}

// separation of control - prevent intermediary callers from cancelling a graph
function outer() {
  // caller cancellation of root, callee cannot cancel root
  let root = new CancellationTokenSource();
  // forward read-only token of root to intermediate caller
  let result = intermediate(root.token);
  // caller can cancel result through root
  root.cancel();
}

function intermediate(token = CancellationToken.none) {
  // cannot directly cancel token (in case it is shared)
  // if intermediate needs to control cancellation of inner, it should create a linked source
  let result = inner(token);
  // cannot directly cancel result (in case it is memoized)
  // if intermediate needs to intercept cancellation of result, it can wrap it in a new promise
  return result;
}

function inner(token = CancellationToken.none) {
  // cannot directly cancel token (in case it is shared)
  // can observe cancellation and react
  return new Promise((resolve, reject) => { token.register(reject); });
}

// observe reason for cancellation
function exec(token = CancellationToken.none) {
  token.register(reason => {
    // here we can observe why we were canceled
  });
}

let source = new CancellationTokenSource();
exec(source.token);
source.cancel(new Error("User canceled."));

// cancellation in an async function
async function fetchAndParse(urls, token = CancellationToken.none) {
  let result = [];
  for (let url of urls) {
    // early exit via throw if we were canceled at start or between each parse
    token.throwIfCanceled();

    let text = await fetch(url, token);
    let parsed = parse(text);
    result.push(parsed);
  }
  return result;
}

// update registration in multi-phase process
async function runSteps(token = CancellationToken.none) {
  let slideIn = new SlideInAnimation();
  
  // reverse the slide-in animation if token is canceled before the animation completes.
  let slideInReg = token.register(() => slideIn.reverse());
  await slideIn.animate();

  // slide-in completed, unregister the callback
  slideInReg.unregister();

  let fadeOut = new FadeOutAnimation();

  // reverse the fade-out animation if token is canceled before the animation completes.
  let fadeOutReg = token.register(() => fadeOut.reverse());
  await fadeOut.animate();

  // fade-out completed, unregister the callback
  fadeOutReg.unregister();
}
Clone this wiki locally