Promises + Generators = ♥️

thanks @liip

Promises

Generators

Combined

Promises

Problem

Solution

getDB((db) => {
  db.get("Martin", (user) => {
    print(user);
  }
});
getDB()
  .then((db) => db.get("Martin"))
  .then(print);

Promise

  • then(fulfillCbk, rejectCbk): Promise
  • catch(rejectCbk)
let getAPromise = function() {
  return new Promise((fulfill, reject) => {
    fulfill("yay!");
  });
};

let getAnAlwaysFulfilledPromise = function() {
  return Promise.resolve("yay!");
};

let doMore = () => getAPromise().then((str) =>{
  if(str == "yay")
    return true;
  else
    throw "Invalid string generated";
});

Promise Chaining

  • then resolves returned Promises
  • return or throw in then handlers are treated as accept/reject
  • Problem: Scoping
Promise.resolve(1).then((number) => number + 2).then((number) => {
  if(number > 3) {
    throw "Exceeded 3";
  }
  else {
    return number / 3;
  }
}).then((number) => getUser(number)).then((user) => {
  if(user.isNice) {
    return getReferences(user).then((references) => {
      user.references = references;
      return user;
    });
  }
  else {
    return user;
  }
});

Problem

Solution

function(length, callback) {
  var i = 0, users = [];
  var done = function(user) {
    users.push(user);
    if(++i == length) {
      callback(users);
    }
  };

  for(var j = 0; j < length; ++j) {
    getUser(j, done);
  }
}
function(length) {
  var indexes = [];
  for(var j = 0; j < length; ++j) {
    indexes.push(j);
  }

  return all(
    indexes.map((i) => getUser(i))
  );
}

Promise.all

  • Takes an array of promises
  • Waits until all promises are resolved
  • Returns the results in an array
  • In a promise

Generators

Problem

Solution

function() {
  let i = 0;
  return function(n) {
    let ret;
    switch(i) {
      case 0:
        ret = 0;
        break;
      case 1:
        ret = n;
        break;
      case 2:
        ret = n - 2;
        break;
    }
    ++i;
    return ret;
  };
}
function*(n) {
  yield 0;
  yield n;
  return n - 2;
}

function*() {}

  • Like a normal function
  • yield, yield*
  • return
  • Returns an Iterator when called

Generator

No Generator

function*(j) {
  var i = 0;
  yield i;
  i += j;
  i += yield i;
  return i;
}
function(j) {
  var i = 0;
  return i;
}
// yield
function(j, i) {
  i += j;
  return i;
}
// yield
function(input, j, i) {
  i += input;
  return i;
}

Iterator Interface

  • next(passInValue): Object
    • { value: "yielded value",
        done: false }
  • throw(exception): Object
    • optional
  • return(passInValue): Object
    • always returns done: true
    • optional

Iterable Protocol

  • Symbol.iterator
    • Most base Objects implement it
  • for(var i of iterator)
  • Spread operator
let generator = function*(initial) {
  let next = yield initial;
  yield next;
  return "done";
};

let iterable = generator("a");

console.log(iterable.next("b")); // { value: "a", done: false }
console.log(iterable.next()); // { value: "b", done: false }
console.log(iterable.next()); // { value: "done", done: true }

// comprehension and spreading
console.log([...(for(i of generator("e")) i+"asy")]);
// ["easy", "undefinedasy"]

Promises + Generators

Problem

Solution

getDB((db) => {
  db.get("Martin", (user) => {
    print(user);
  }
});
async(function*(arg) {
  var db = yield getDB();
  var user = yield db.get("Martin");
  print(user);
})

Wrapper Function

function async(makeGenerator){
  return function () {
    var generator = makeGenerator.apply(this, arguments);

    function handle(result){
      // result => { done: [Boolean], value: [Object] }
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(function (res){
        return handle(generator.next(res));
      }, function (err){
        return handle(generator.throw(err));
      });
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  }
}
let updateChannels = async(function*(channels) {
  let [data, info] = yield Promise.all([
    qs.queueRequest(baseURL+"all", headers, requeue),
    qs.queueRequest(infoURL+infoArgs, headers, requeue)
  ]);

  if(data.status_code == 200 && info.status_code == 200) {
    let followedChannels = data.items.filter((status) => {
      return channels.some((channel) => status.name == channel.login);
    });

    return Promise.all(followedChannels.map((status) => {
      return getChannelFromJSON(info.items.find((ch) => ch.id == status.id))
    });
  }
});
let updateChannels = function(channels, callback) {
  qs.queueRequest(baseURL+"all", headers, requeue, function(data) {
    qs.queueRequest(infoURL+infoArgs, headers, requeue, function(info) {
      if(data.status_code == 200 && info.status_code == 200) {
        Promise.all(data.items.filter((status) => {
          return channels.some((channel) => status.name == channel.login);
        }).map(function(status) {
          let channelInfo = info.items.find((ch) => ch.id == status.id);
          return getChannelFromJSON(channelInfo))
        })).then(callback);
      }
    });
  });
};

You want worse?

Future

ES2016

  • async function

Thank You For Your Attention

Appendix

function(length) {
  var indexes = Array.from(function*() {
    var i = 0;
    while(i < length) {
      yield i++;
    }
  });

  all(indexes.map((i) => getUser(i)));
}

Docs