Skip to content

Instantly share code, notes, and snippets.

@chadfurman
Last active February 10, 2017 08:53
Show Gist options
  • Save chadfurman/cd9ab09a533c0051b6a26bb04a5937bf to your computer and use it in GitHub Desktop.
Save chadfurman/cd9ab09a533c0051b6a26bb04a5937bf to your computer and use it in GitHub Desktop.
h7

Callbacks

  • Agenda
    • Async Patterns (i.e. Callbacks)
    • Generators / Coroutines
    • Promises
setTimeout(function(){
  console.log("callback!");
}, 1000);
  • We think of this in a synchronous manner. "I'm going to do something, wait 1000ms, and then do something else"

  • Our brains don't work asynchronously, we just switch contexts quickly

  • Callback Hell

setTimeout(function() {
  console.log("one");
  setTimeout(function(){
    console.log("two");
    setTimeout(function() {
      console.log('three');
    },1000);
   },1000);
}, 1000);
  • It's not callback-hell because of the indentation...
  function one(cb) {
    console.log("one");
    setTimeout(cb, 1000);
  }
  function two(cb) {
    console.log("two");
    setTimeout(cb, 1000);
  }
  function three() {
    console.log("three");
  }
  
  one(function() {
    two(three);
  });
  • The above is still callback hell. Why?
    • Inversion of Control
    • As soon as we take our program, wrap it in a function, and hand it off to another function, we lose control.
    • There are certain expectations we have when we work with callbacks, and it can add confusion to the code.
  • Let's imagine you add a callback to a third-party API
    • You pass a callback to a library
    • The library calls your callback more times you expect it to, and it wasn't documented
    • Things can get hidden / confusing
function trySomething(ok, err) {
  setTimeout(function() {
    var num = Math.random();
    if (num > 0.5) ok(num);
    else err(num);
  }, 1000);
}
trySomething(
  function(num) {
    console.log("Success: " + num);
  },
  function(num) {
    console.log("Failure: " + num);
  },
);
  • The above doesn't solve IoC -- it makes it worse. What if both callbacks were called?

  • What about this "error=first" aka "node" style?

function trySomething(cb) {
  setTimeout(function() {
    var num = Math.random();
    if (num > 0.5) cb(null, num);
    else cb("Too low!");
  }, 1000);
}

trySomething(function(err, num) {
  if (err) {
    console.log(err);
  } else {
    console.log("Number: " + num);
  }
});
  • What if the Error and the success value are passed back? You'd probably ignore the success value.
  • The above does doesn't solve anything.
function getData(d, cb) {
  setTimeout(function() { cb(d); }, 1000);
}

getData(10,function(num1)[
  var x = 1 + num1;
  getData(30, function(num2){
    var y = 1 + num2;
    getData(
      "Meaning of life: " + (x + y)),
      function(answer) {
        console.log(answer); // Meaning of life: 42
      }
    );
  });
});

Still a mess!!!

Generators

  • Asynchronous code in a synchronous manner...
  • This breaks an assumption of our JS code
    • As soon as a function starts executing, we assume the function will finish all lines of its code before other code runs.
    • That's true of normal functions, but not true of generators
    • Generators pause themselves in the middle and can be resumed later.
function* gen() {
  console.log("Hello");
  yield null;
  console.log("World");
}

var it = gen(); // this doesn't actually run our function, it just creates an iterator
it.next(); // prints "Hello"
it.next(); // prints World"
  • On line 7, var it = gen(); we haven't executed anything yet, just made an iterator.
  • The iterator controls operation of our function
  • On line 8, it.next(), we start the generator and it runs until the first yield statement
  • We can call it.next() again and it will resume from where it paused and run to the next yield statement
var run = coroutine(function* () {
  var x = 1 + (yeild null);
  var y = 1 + (yield null);
  yield (x + y);
});

run();
run10);
console.log("Meaning of life: " + run(30).value);
  • Looking at the above, yield is a two-way message-passing method

  • on line 7, when we run() first, yield gives us a null value.

  • So that null value is the return value of that run() call

  • when I say run(10) next, the yield expression we stopped on gets replaced with 10

  • this results in var x = 1 + (10) getting executed, so x = 11

  • Then it goes to var y = 1 + (yield null) -- thus, the return value of run(10) is null

  • Then we can say run(30), resutling in var y = 1 + 30 and so y = 31

  • the code then continues to execute upto the next yield expression, stopping on yield (x + y) and returning 31+11 = 42

  • the return value of run(30) becomes 42

  • But how does this help with async?

  • Well, our steps do not synchronously iterate.

function getData(d) {
  setTimeout(function() { run(d); }, 1000);
}

var run = coroutine(function*() {
  var x = 1 + (yield getData(10));
  var y = 1 + (yield getData(30));
  var answer = 1 + (yield getData("Meaning of life: " + (x + y)));
  console.log(answer);
});

run();
  • The above might look synchronous, but it's actually async

Promises

  • Lots of ways people explain promises
  • Here's two metaphores:
    1. Order number / receipt
    • You give casheir some money for some food
    • Casheir hands you a receipt with an order number
    • Casheir calls out your number, you give them the receipt and they give you the food
    • Promises will return a "receipt" which you can "exchange" for the value you asked for when it's ready
    1. Continuation events
    • What if we could subscribe to an event that lets us know when that function finishes?
    • That's essentially what a promise is
    • We call the function, it doesn't finish
    • we subscribe to a "continuation event" which fires when the function finishes

This is how jQuery does it... (not standard)

var wait = jQuery.Deferred(); // a 'deferred' object is the receipt
var p = wait.promise(); // this is getting our continuation event

p.done(function(value) { // here we subscribe to the continuation event
  console.log(value);
});

setTimeout(function() {
  wait.resolve(Math.random()); // this is where we say "order is ready", or where we trigger the continuation event
}, 1000);
  • Below is an example of how we'd use the above...
function waitForN(n) {
  var d = $.Deferred();
  setTimeout(d.resolve, n);
  return d.promise();
}

waitForN(1000)
.then(function(){
  console.log("Hello world");
  return waitForN(2000);
})
.then(function() {
  console.log("finally!");
});
  • The above is very synchronous-looking syntax for an asynchronous series of tasks.
  • When I pass a callback into a utility, the utility has control over when my callback is invoked.
  • With a promise, the utility gives us a promise back.
  • We listen to the promise and decide what to do next. We are now in control.

Promises are built into ES6 and they're already there in Chrome.

function getData(d) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() { resolve(d); }, 1000);
  });
}

var x;

getData(10)
.then(function(num1) {
  x = 1 + num1;
  return getData(30);
})
.then(function(num2) {
  var y = 1 + num2;
  return getData("Meaning of life: " + (x + y));
})
.then(function(answer){
  console.log(answer);
});

Asynquence

  • A library written by Kyle
  • useful for more complex tasks -- a Promise-abstraction library.
  • examples of Promise-abstraction libraries: async, queue, asyncquence
  • Squence = automatically chained promises
ASQ()
.then(function(done)[
  setTimeout(done, 1000);
})
.gate( // two or more things happen at the same time, wait for everyone to finish in whatevre order
  function(done){
    setTimeout(done, 1000);
  },
  function(){
    setTimeout(done, 1000);
  }
)
.then(function(){
  console.log("2 seconds passed!");
});

another example

function getData(d) {
  return ASQ(function(done){
    setTimeout(function(){ done(d); }, 1000);
  });
}

ASQ()
.waterfall(
  function(done){ getData(10).pipe(done); },
  function(done({ getData(30).pipe(done); }
)
.seq(function(num1, num2) {
  var x = 1 + num1;
  var y = 1 + num2;
  return getData("Meaning of life: " + (x + y));
})
.val(function(answer) {
  console.log(answer);
  // Meaning of life: 42
});

What about Generators + Promises? Well this is also built into ASQ (runner)

function getData(d) {
  return ASQ(function(done) {
    setTimeout(function(){ done(d); }, 1000);
  });
}

ASQ()
.runner(function*() {
  var x = 1 + (yield getDAta(10));
  var y = 1 + (yield getDAta(30));
  var answer = yield (getData(
    "Meaning of life: " + (x + y)
  ));
  return answer
})
.val(function(answer){
  console.log(asnwer); // meaning of life...
});

Here's something else cool...

ASQ(2)
.runner(
  // v1 will be set as `2` here
  function*(v1) {
    console.log("initial v1:" + v1); // initial v1: 2
    v1 += yield null;
    console.log("now v1:" + v1); // now v1: 12
    v1 += yield getData(v1 * 2);
    console.log("finally v1:" + v1); // finally v1: 84
  },
  function*() {
    var v2 = yield getData(10);
    console.log("initial v2: " + v2); // initial v2: 24
    v2 += yield getData(v2 *3);
    console.log("finally v2: " + v2); // finally v2: 109
    yielf getData(v2 + 6);
  }
)
.val(function(msg) {
  console.log("Result: " + msg); // result 115
});

Quiz:

  1. What is "callback hell"? Why do callbacks suffer from "inversion of control"?
  • Giving up control of your program to another function
  1. How do you pause a generator? How do you resume it?
  • yield and next respectively
  1. What is a promise? How does it solve inversion of control issues?
  • It's a "promise" for a future value, a method of continuing control.
  • Rather than passing a method in, we get the value out
  • It inverts the inversion of control
  1. How do we combine generators and promises for flow control?
  • The generator yields out promises
  • When the promise finishes, it restarts the generator
  • ASQ's method is called runner

Exercise 5

  • Load three seperate files in parallel
  • print them in the proper order
  • It's currently implemented as callbacks
  • Use promises or genrators or whatever to reimplement it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment