Promise monad in ES6

ES6 is introducing classes to JavaScript. You can turn on the new features in Chrome by enabling chrome://flags/#enable-javascript-harmony or in v8 with v8 –harmony.

Functor = class Functor {
  constructor() {
    if (new.target === Functor) {
      throw new TypeError(
          'Cannot construct Functor instances directly');
    }
    if (typeof(this.map) !== 'function' ||
        typeof(new.target.map) !== 'function') {
      throw new TypeError('Must provide static and member map.')
    }
  }
};

// Base monad class
Monad = class Monad extends Functor {
  constructor() {
    super();
    if (new.target === Monad) {
      throw new TypeError(
          'Cannot construct Monad instances directly');
    }
    if (typeof(this.lift) !== 'function' ||
        typeof(new.target.lift) !== 'function') {
      throw new TypeError('Must provide static and member lift.')
    }
    if (typeof(this.flatten) !== 'function' ||
        typeof(new.target.flatten) !== 'function') {
      throw new TypeError('Must provide static and member flatten.')
    }
  }
  flatMap(f) {
    return this.map(f).flatten();
  }
  static flatMap(f) {
    return (mx) => mx.flatMap(f);
  }
};

// Promise monad
P = class P extends Monad {
  // Constructor expects a promise to wrap.
  constructor(p) {
    super();
    if (p && typeof(p.then) === 'function') {
      this.p = p;
    } else {
      throw new TypeError(
        'Expected an object with a "then" method.');
    }
  }
  static map(f) {
    return (px) => px.map(f);
  }
  map(f) {
    return new P(this.p.then(f));
  }
  static lift(v) {
    return new this(new Promise((resolve) => resolve(v)));
  }
  lift() { return this.constructor.lift(this); }
  static flatten(ppv) {
    return new this(new Promise((resolve) => {
      return ppv.map((pv) => pv.map(resolve))
    }));
  }
  flatten() { return this.constructor.flatten(this); }
};

// XHR + Promise
XHRP = (url) => new P(new Promise((resolve) => {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = () => {
    if (xhr.readyState == XMLHttpRequest.DONE) {
      if (xhr.status == 200) {
        resolve(xhr.responseText);
      }
    }
  };
  xhr.open('GET', url, true);
  xhr.send();
}));

// Search Google for your ip address
XHRP('http://jsonip.com').flatMap((json) =>
  XHRP('http://google.com/search?' + JSON.parse(json).ip).map((results) =>
    console.log(results)));

// New experimental GlobalFetch already returns promise
if (GlobalFetch) {
  GFP = (url) => new P(GlobalFetch.fetch(url));

  // Search Google for your ip address
  GFP('http://jsonip.com').flatMap((json) =>
    GFP('http://google.com/search?' + JSON.parse(json).ip).map((results) =>
      console.log(results)));
}

// setTimeout + Promise
DelayP = (value, delay) => new P(new Promise((resolve) =>
    setTimeout(() => resolve(value), delay)));

DelayP(1, 1000).flatMap((v) =>
  // Runs at t=1 sec, v = 1
  DelayP(2+v, 2000).flatMap((w) =>
    // Runs at t=3 sec, w = 3
    DelayP(3+v+w, 3000).map((x) =>
      // Runs at t=6 sec, x = 7
      // Logs 15
      console.log(4+v+w+x))));

DelayP(1, 1000)
    // Runs at t=1 sec, v=1
    .map((v) => 2+v)
    // Runs at t=1 sec, w=3
    .map((w) => 3+w)
    // Runs at t=1 sec, x=6
    // Logs 10
    .map((x) => console.log(4+x));

Leave a comment