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

Promises + Generators = ♥️

By Martin Giger

Promises + Generators = ♥️

Asynchronous JavaScript is changing a lot with the upcoming ES2015 (also known as ES6) standard. This talk introduces you to promises and the versatile generators. It also explains Iterables, which are the generic protocol implemented by generators are used throughout ES2015. When generators and promises are combined they result in awesome asynchronous code!

  • 2,681