SlideShare a Scribd company logo
Callbacks, Promises, and Coroutines
                  (oh my!)


     Asynchronous Programming
        Patterns in JavaScript


                Domenic Denicola
           http://domenicdenicola.com
                @domenicdenicola
In non-web languages,
most of the code we write is synchronous.

                aka blocking
Console.WriteLine("What is your name?");
string name = Console.ReadLine();
Console.WriteLine("Hello, " + name);
var fileNames = Directory.EnumerateFiles("C:");

foreach (var fileName in fileNames)
{
  using (var f = File.Open(fileName, FileMode.Open))
  {
    Console.WriteLine(fileName + " " + f.Length);
  }
}
using (var client = new WebClient())
{
  string html =
client.DownloadString("http://news.ycombinator.com");

    Console.WriteLine(html.Contains("Google"));
    Console.WriteLine(html.Contains("Microsoft"));
    Console.WriteLine(html.Contains("Apple"));
}
Thread.Start                               BackgroundWorker


               Control.InvokeRequired



        This often causes us some pain…

       … but hey, there’s always threads!

                       Dispatcher.Invoke



ThreadPool

                                              .AsParallel()
Q: What are these threads doing, most of the time?


A: waiting
WTF!?
In JavaScript, we do things differently.
There’s only one thread in JavaScript,
so we use that thread to get stuff done.
OK, let’s talk about…
•   The event loop
•   Callbacks
•   Promises
•   Coroutines
THE EVENT LOOP
You’ve seen event loops before:
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0) > 0)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
this.btnOK.Click += this.btnOK_Click;


private void btnOK_Click(object sender,
                         EventArgs e)
{
  // ...
}
$("#ok-button").click(function () {
    // ...
});

setTimeout(function () {
    // ...
}, 100);

$.get("http://example.com", function (result) {
    // ...
});
Some event loop subtleties
•   Yielding
•   Async’s not sync
•   Errors
•   It’s not magic
Event Loop Subtleties

   Yielding
   console.log("1");

   $.get("/echo/2", function (result) {
     console.log(result);
   });

   console.log("3");

   // 1, 3, 2
Event Loop Subtleties

   Async’s not sync
   var hi = null;

   $.get("/echo/hi", function (result) {
     hi = result;
   });

   console.log(hi);

   // null
Event Loop Subtleties

   Errors
   console.log("About to get the website...");

   $.ajax("http://sometimesdown.example.com", {
     success: function (result) {
        console.log(result);
     },
     error: function () {
        throw new Error("Error getting the website");
     }
   });

   console.log("Continuing about my business...");
Event Loop Subtleties

   It’s not magic
   function fib(n) {
       return n < 2 ? 1 : fib(n-2) + fib(n-1);
   }

   console.log("1");

   setTimeout(function () {
      console.log("2");
   }, 100);

   fib(40);

   // 1 ... 15 seconds later ... 2

http://teddziuba.com/2011/10/node-js-is-cancer.html
The event loop is tricky… but powerful.
CALLBACKS
What we’ve seen so far has been doing
  asynchronicity through callbacks.
Callbacks are OK for simple operations, but
 force us into continuation passing style.
Recurring StackOverflow question:
function getY() {
  var y;
  $.get("/gety", function (jsonData) {
    y = jsonData.y;
  });
  return y;
}
                      Why doesn’t it work???
var x = 5;
var y = getY();

console.log(x + y);
After getting our data, we have to do
 everything else in a continuation:
function getY(continueWith) {
    $.get("/gety", function (jsonData) {
      continueWith(jsonData.y);
    });
}


var x = 5;
getY(function (y) {
    console.log(x + y);
});
CPS Headaches
• Doing things in sequence is hard
• Doing things in parallel is harder
• Errors get lost easily
CPS Headaches

  Doing things in sequence is hard
  $("#button").click(function () {
    promptUserForTwitterHandle(function (handle) {
      twitter.getTweetsFor(handle, function (tweets) {
        ui.show(tweets);
      });
    });
  });
CPS Headaches

  Doing things in parallel is harder
  var tweets, answers, checkins;

  twitter.getTweetsFor("domenicdenicola", function (result) {
    tweets = result;
    somethingFinished();
  });
  stackOverflow.getAnswersFor("Domenic", function (result) {
    answers = result;
    somethingFinished();
  });
  fourSquare.getCheckinsBy("Domenic", function (result) {
    checkins = result;
    somethingFinished();
  });
CPS Headaches

  Doing things in parallel is harder
  var finishedSoFar = 0;

  function somethingFinished() {
    if (++finishedSoFar === 3) {
      ui.show(tweets, answers, checkins);
    }
  }
CPS Headaches

  Errors get lost easily
  function getTotalFileLengths(path, callback) {
    fs.readdir(path, function (err, fileNames) {
      var total = 0;

       var finishedSoFar = 0;
       function finished() {
         if (++finishedSoFar === fileNames.length) {
           callback(total);
         }
       }

        fileNames.forEach(function (fileName) {
          fs.readFile(fileName, function (err, file) {
            total += file.length;
            finished();
          });
        });
      });
  }
You could write your own library to make this nicer…
function parallel(actions, callback) {
  var results = [];
  function finished(result) {
    results.push(result);
    if (results.length === actions.length) {
        callback(results);
    }
  }

    actions.forEach(function (action) {
      action(finished);
    });
}
parallel([
  function (cb) {
     twitter.getTweetsFor("domenicdenicola", cb);
  },
  function (cb) {
     stackOverflow.getAnswersFor("Domenic", cb);
  },
  function (cb) {
     fourSquare.getCheckinsFor("Domenic", cb);
  }
], function (results) {
  console.log("tweets = ", results[0]);
  console.log("answers = ", results[1]);
  console.log("checkins = ", results[2]);
});
And in fact many people have:




https://github.com/joyent/node/wiki/modules#wiki-async-flow
The best of these (IMO) are based on
  an abstraction called “promises”
PROMISES
Un-inverts the chain of responsibility:
instead of calling a passed callback, return a promise.
addWithCallback(a, b, function (result) {
  assert.equal(result, a + b);
});

var promise = addWithPromise(a, b);

promise.then(function (result) {
  assert.equal(result, a + b);
});
Why promises are awesome

• Cleaner method signatures
• Uniform return/error semantics
• Easy composition
• Easy sequential/parallel join
• Always async
• Exception-style error bubbling
Promises are Awesome

  Cleaner method signatures
  Uniform return/error semantics
  $.get(
    url,
    [data],
    [success(data, status, xhr)],
    [dataType]
  )
Promises are Awesome

  Cleaner method signatures
  Uniform return/error semantics
  $.ajax(url, settings)

  settings.success(data, status, xhr)
  settings.error(xhr, status, errorThrown)
  settings.complete(xhr, status)
Promises are Awesome

  Cleaner method signatures
  Uniform return/error semantics
  fs.open(
    path,
    flags,
    [mode],
    [callback(error, file)]
  )
Promises are Awesome

  Cleaner method signatures
  Uniform return/error semantics
  fs.write(
    file,
    buffer,
    offset,
    length,
    position,
    [callback(error, written, buffer)]
  )
Promises are Awesome

  Cleaner method signatures
  Uniform return/error semantics
  getAsPromise(url, [data], [dataType]).then(
    function onFulfilled(result) {
       var data = result.data;
       var status = result.status;
       var xhr = result.xhr;
    },
    function onBroken(error) {
       console.error("Couldn't get", error);
    }
  );
Promises are Awesome

  Easy composition

  function getUser(userName, onSuccess, onError) {
    $.ajax("/user?" + userName, {
      success: onSuccess,
      error: onError
    });
  }
Promises are Awesome

  Easy composition

  function getUser(userName) {
    return getAsPromise("/user?" + userName);
  }
Promises are Awesome

  Easy composition

  function getFirstName(userName, onSuccess, onError) {
    $.ajax("/user?" + userName, {
      success: function successProxy(data) {
         onSuccess(data.firstName);
      },
      error: onError
    });
  }
Promises are Awesome

  Easy composition

  function getFirstName(userName) {
    return getAsPromise("/user?" + userName)
             .get("firstName");
  }
Promises are Awesome

  Easy sequential join

  $("#button").click(function () {
    promptUserForTwitterHandle(function (handle) {
      twitter.getTweetsFor(handle, function (tweets) {
        ui.show(tweets);
      });
    });
  });
Promises are Awesome

  Easy sequential join

  $("#button").clickPromise()
    .then(promptUserForTwitterHandle)
    .then(twitter.getTweetsFor)
    .then(ui.show);
Promises are Awesome

  Easy parallel join

  var tweets, answers, checkins;

  twitter.getTweetsFor("domenicdenicola", function (result) {
    tweets = result;
    somethingFinished();
  });
  stackOverflow.getAnswersFor("Domenic", function (result) {
    answers = result;
    somethingFinished();
  });
  fourSquare.getCheckinsBy("Domenic", function (result) {
    checkins = result;
    somethingFinished();
  });
Promises are Awesome

  Easy parallel join

  Q.all([
    twitter.getTweetsFor("domenicdenicola"),
    stackOverflow.getAnswersFor("Domenic"),
    fourSquare.getCheckinsBy("Domenic")
  ]).then(function (results) {
    console.log(results[0], results[1], results[2]);
  });
Promises are Awesome

  Easy parallel join

  Q.all([
    twitter.getTweetsFor("domenicdenicola"),
    stackOverflow.getAnswersFor("Domenic"),
    fourSquare.getCheckinsBy("Domenic")
  ]).spread(function (tweets, answers, checkins) {
    console.log(tweets, answers, checkins);
  });
Promises are Awesome

  Always async

  function getUser(userName, onSuccess, onError) {
    if (cache.has(userName)) {
      onSuccess(cache.get(userName));
    } else {
      $.ajax("/user?" + userName, {
        success: onSuccess,
        error: onError
      });
    }
  }
Promises are Awesome

  Always async

  console.log("1");

  getUser("ddenicola", function (user) {
    console.log(user.firstName);
  });

  console.log("2");

  // 1, 2, Domenic
Promises are Awesome

  Always async

  console.log("1");

  getUser("ddenicola", function (user) {
    console.log(user.firstName);
  });

  console.log("2");

  // 1, Domenic, 2
Promises are Awesome

  Always async

  function getUser(userName) {
    if (cache.has(userName)) {
      return Q.ref(cache.get(userName));
    } else {
      return getWithPromise("/user?" + userName);
    }
  }
Promises are Awesome

  Always async

  console.log("1");

  getUser("ddenicola").then(function (user) {
    console.log(user.firstName);
  });

  console.log("2");

  // 1, 2, Domenic (every time)
Promises are Awesome

  Exception-style error bubbling

  getUser("Domenic", function (user) {
    getBestFriend(user, function (friend) {
      ui.showBestFriend(friend);
    });
  });
Promises are Awesome

  Exception-style error bubbling

  getUser("Domenic", function (err, user) {
    if (err) {
      ui.error(err);
    } else {
      getBestFriend(user, function (err, friend) {
        if (err) {
          ui.error(err);
        } else {
          ui.showBestFriend(friend);
        }
      });
    }
  });
Promises are Awesome

  Exception-style error bubbling

  getUser("Domenic")
    .then(getBestFriend, ui.error)
    .then(ui.showBestFriend, ui.error);
Promises are Awesome

  Exception-style error bubbling

  getUser("Domenic")
    .then(getBestFriend)
    .then(ui.showBestFriend, ui.error);
Promises are Awesome

  Exception-style error bubbling

  ui.startSpinner();
  getUser("Domenic")
    .then(getBestFriend)
    .then(
       function (friend) {
          ui.showBestFriend(friend);
          ui.stopSpinner();
       },
       function (error) {
          ui.error(error);
          ui.stopSpinner();
       }
    );
Promises are Awesome

  Exception-style error bubbling

  ui.startSpinner();
  getUser("Domenic")
    .then(getBestFriend)
    .then(ui.showBestFriend, ui.error)
    .fin(ui.stopSpinner);
Promises are Awesome

  Exception-style error bubbling

  function getBestFriendAndDontGiveUp(user) {
    return getUser(user).then(
      getBestFriend,
      function (error) {
        if (error instanceof TemporaryNetworkError) {
          console.log("Retrying after error", error);
          return getBestFriendAndDontGiveUp(user);
        }
        throw error;
      });
  }
Sounds great. How do I get in on this action?
Use Q
•   By Kris Kowal, @kriskowal
•   https://github.com/kriskowal/q
•   Can consume promises from jQuery etc.
•   Implements various CommonJS standards
If you’re already using jQuery’s promises, switch to Q:


    https://github.com/kriskowal/q/wiki/jQuery
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patterns in JavaScript
Creating promises with Q

   Fulfilling promises
   // We have:
   setTimeout(doSomething, 1000);

   // We want:
   delay(1000).then(doSomething);
Creating promises with Q

   Fulfilling promises
   function delay(ms) {
     var deferred = Q.defer();
     setTimeout(deferred.resolve, ms);
     return deferred.promise;
   }

   delay(1000).then(doSomething);
Creating promises with Q

   Breaking promises
   function getWithTimeout(url, ms, onSuccess, onError) {
     var isTimedOut = false, isHttpErrored = false;

       setTimeout(function () {
         if (!isHttpErrored) {
           isTimedOut = true;
           onError(new Error("timed out"));
         }
       }, ms);

       $.ajax(url, {
         success: function (result) {
           if (!isTimedOut) { onSuccess(result); }
         },
         error: function (xhr, status, error) {
           if (!isTimedOut) {
             isHttpErrored = true;
             onError(error);
           }
         }
       });
   }
Creating promises with Q

   Breaking promises
   function getWithTimeout(url, ms) {
     var deferred = Q.defer();

       setTimeout(function () {
         deferred.reject(new Error("timed out"));
       }, ms);

       $.ajax(url, {
         success: deferred.resolve,
         error: deferred.reject
       });

       return deferred.promise;
   }
Creating promises with Q

   Building abstractions
   function timeout(promise, ms) {
     var deferred = Q.defer();
     promise.then(deferred.resolve, deferred.reject);

       setTimeout(function () {
         deferred.reject(new Error("timed out"));
       }, ms);

       return deferred.promise;
   }

   function getWithTimeout(url, ms) {
     return timeout(getAsPromise(url), ms);
   }
Promises are cool.
They clean up our method signatures.


They’re composable, they’re joinable,
    and they’re dependably async.


They unify various callback conventions
 into something very much like return
        values and exceptions.
But… we still have to write in CPS.
COROUTINES
“Coroutines are computer program
            components that generalize subroutines
                 to allow multiple entry points for
             suspending and resuming execution at
                             certain locations.”



http://en.wikipedia.org/wiki/Coroutine
WTF!?
Nice:
var xP = getX();
var yP = getY();
var zP = getZ();

Q.all([xP, yP, zP]).spread(function (x, y, z) {
  console.log(x + y + z);
});
Nicer:
var [x, y, z] = await Q.all([getX(), getY(), getZ()]);

console.log(x + y + z);
Nice:
$("#button").clickPromise()
  .then(promptUserForTwitterHandle)
  .then(twitter.getTweetsFor)
  .then(ui.show);
Nicer:
await $("#button").clickPromise();

var handle = await promptUserForTwitterHandle();
var tweets = await twitter.getTweetsFor(handle);

ui.show(tweets);
Q: Can’t the compiler do this for me?


A: yes… if you are willing to introduce a compiler.
Several options, none perfect
•   Kaffeine: http://weepy.github.com/kaffeine/
•   Traceur: http://tinyurl.com/traceur-js
•   TameJS: http://tamejs.org/
•   Node fork: http://tinyurl.com/node-async
Q: OK well… can’t the interpreter do this for me?


A: yes… if you’re willing to wait for the next version of JS.
The next version of JavaScript (“ECMAScript
Harmony”) has a limited form of coroutines that can
  be twisted to do something like what we want.
ECMAScript Harmony generators
 function* fibonacci() {
   var [prev, curr] = [0, 1];
   for (;;) {
     [prev, curr] = [curr, prev + curr];
      yield curr;
   }
 }

 for (n of fibonnaci()) {
   console.log(n);
 }



http://wiki.ecmascript.org/doku.php?id=harmony:generators
ECMAScript Harmony generators
 var eventualAdd = Q.async(function* (pA, pB) {
   var a = yield pA;
   var b = yield pB;
   return a + b;
 });




https://github.com/kriskowal/q/tree/master/examples/async-generators
ECMAScript Harmony generators
 // Can only use yield as we want to within
 // Q.async'ed generator functions

 Q.async(function* () {
   // Talk to the server to get one and two.
   var three = yield eventualAdd(getOne(), getTwo());

   assert.equal(three, 3);
 })();




https://groups.google.com/d/topic/q-continuum/7PWKbgeFA48/discussion
ECMAScript Harmony generators
 // Given promise-returning delay(ms) as before:

 var animateAsync = Q.async(function* (el) {
   for (var i = 0; i < 100; ++i) {
     element.style.left = i;
     yield delay(20);
   }
 });




http://wiki.ecmascript.org/doku.php?id=strawman:async_functions
ECMAScript Harmony generators
 Q.async(function* () {
   var el = document.getElementById("my-element");

    yield animateAsync(el);

   console.log("it's done animating");
 })();




https://groups.google.com/d/topic/q-continuum/7PWKbgeFA48/discussion
So coroutines are a bit of a mess, but
   we’ll see how things shape up.
Recap
•   Async is here to stay
•   But you don’t have to dive into callback hell
•   Use promises
•   Use Q
•   Maybe use coroutines if you’re feeling brave
Thanks for listening!

More Related Content

PDF
MERN Stack Developer Roadmap By ScholarHat PDF
PDF
React JS Hooks Sheet .pdf
PDF
ReactJS Tutorial For Beginners | ReactJS Redux Training For Beginners | React...
PDF
React Js Simplified
PDF
Fundamental of Node.JS - Internship Presentation - Week7
PDF
PDF
MERN Stack Roadmap for Beginner PDF By ScholarHat
PDF
Introduction to react-query. A Redux alternative? (Nikos Kleidis, Front End D...
MERN Stack Developer Roadmap By ScholarHat PDF
React JS Hooks Sheet .pdf
ReactJS Tutorial For Beginners | ReactJS Redux Training For Beginners | React...
React Js Simplified
Fundamental of Node.JS - Internship Presentation - Week7
MERN Stack Roadmap for Beginner PDF By ScholarHat
Introduction to react-query. A Redux alternative? (Nikos Kleidis, Front End D...

What's hot (20)

PDF
Understanding Asynchronous JavaScript
PDF
Introduction to RxJS
PPTX
PPTX
Promises, Promises
PPTX
Introduction to RxJS
PPTX
JS Event Loop
PDF
React Router: React Meetup XXL
PDF
Asynchronous JavaScript Programming with Callbacks & Promises
PDF
TypeScript - An Introduction
PPTX
PPTX
Angular 2.0 forms
PDF
Angular & RXJS: examples and use cases
PDF
JavaScript - Chapter 15 - Debugging Techniques
PDF
Workshop 4: NodeJS. Express Framework & MongoDB.
PDF
Intro to Asynchronous Javascript
PPTX
Intro to React
PPTX
React.js - The Dawn of Virtual DOM
PDF
JavaScript Promises
PDF
Introduction to kotlin coroutines
PDF
React js
Understanding Asynchronous JavaScript
Introduction to RxJS
Promises, Promises
Introduction to RxJS
JS Event Loop
React Router: React Meetup XXL
Asynchronous JavaScript Programming with Callbacks & Promises
TypeScript - An Introduction
Angular 2.0 forms
Angular & RXJS: examples and use cases
JavaScript - Chapter 15 - Debugging Techniques
Workshop 4: NodeJS. Express Framework & MongoDB.
Intro to Asynchronous Javascript
Intro to React
React.js - The Dawn of Virtual DOM
JavaScript Promises
Introduction to kotlin coroutines
React js
Ad

Viewers also liked (9)

PPTX
W3C Automotive 표준 개발 현황
PPTX
[하코사세미나]미리보는 대규모 자바스크립트 어플리케이션 개발
PPTX
[하코사세미나] 한 시간 만에 배우는 Jquery
PPTX
[ 하코사세미나] 의외로 쉬운 D3 그래프 퍼블리싱
PDF
CSS 실무테크닉
PDF
CSS 셀렉터
PDF
[Hello world 오픈세미나]실시간웹을위한comet과socket.io
PDF
[Hello world 오픈 세미나]ffmpeg android
PDF
[Hello world 오픈 세미나]oauth
W3C Automotive 표준 개발 현황
[하코사세미나]미리보는 대규모 자바스크립트 어플리케이션 개발
[하코사세미나] 한 시간 만에 배우는 Jquery
[ 하코사세미나] 의외로 쉬운 D3 그래프 퍼블리싱
CSS 실무테크닉
CSS 셀렉터
[Hello world 오픈세미나]실시간웹을위한comet과socket.io
[Hello world 오픈 세미나]ffmpeg android
[Hello world 오픈 세미나]oauth
Ad

Similar to Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patterns in JavaScript (20)

PDF
Event driven javascript
PDF
Event driven javascript
PDF
Douglas Crockford: Serversideness
PDF
Testing web APIs
PDF
Event Driven Javascript
PPT
You promise?
PPTX
Avoiding Callback Hell with Async.js
PDF
The Evolution of Async-Programming (SD 2.0, JavaScript)
PDF
How to actually use promises - Jakob Mattsson, FishBrain
PPTX
Avoiding callback hell in Node js using promises
ODP
Node js
PDF
Promises are so passé - Tim Perry - Codemotion Milan 2016
PDF
Async js - Nemetschek Presentaion @ HackBulgaria
PDF
Kamil witecki asynchronous, yet readable, code
PDF
The art of concurrent programming
PDF
Angular promises and http
PDF
The evolution of asynchronous JavaScript
PDF
Think Async: Asynchronous Patterns in NodeJS
PPTX
Async discussion 9_29_15
PPTX
All you need to know about the JavaScript event loop
Event driven javascript
Event driven javascript
Douglas Crockford: Serversideness
Testing web APIs
Event Driven Javascript
You promise?
Avoiding Callback Hell with Async.js
The Evolution of Async-Programming (SD 2.0, JavaScript)
How to actually use promises - Jakob Mattsson, FishBrain
Avoiding callback hell in Node js using promises
Node js
Promises are so passé - Tim Perry - Codemotion Milan 2016
Async js - Nemetschek Presentaion @ HackBulgaria
Kamil witecki asynchronous, yet readable, code
The art of concurrent programming
Angular promises and http
The evolution of asynchronous JavaScript
Think Async: Asynchronous Patterns in NodeJS
Async discussion 9_29_15
All you need to know about the JavaScript event loop

More from Domenic Denicola (20)

PPTX
The State of JavaScript (2015)
PPTX
Async Frontiers
PPTX
The jsdom
PPTX
The Final Frontier
PPTX
ES6 in Real Life
PPTX
Streams for the Web
PPTX
After Return of the Jedi
PPTX
The State of JavaScript
PPTX
How to Win Friends and Influence Standards Bodies
PPTX
The Extensible Web
PPTX
The Promised Land (in Angular)
PDF
ES6: The Awesome Parts
PDF
Boom! Promises/A+ Was Born
PPTX
PPTX
Client-Side Packages
PPTX
Creating Truly RESTful APIs
PPTX
JavaScript on the Desktop
PPTX
ES6 is Nigh
PPTX
Real World Windows 8 Apps in JavaScript
PDF
Unit Testing for Great Justice
The State of JavaScript (2015)
Async Frontiers
The jsdom
The Final Frontier
ES6 in Real Life
Streams for the Web
After Return of the Jedi
The State of JavaScript
How to Win Friends and Influence Standards Bodies
The Extensible Web
The Promised Land (in Angular)
ES6: The Awesome Parts
Boom! Promises/A+ Was Born
Client-Side Packages
Creating Truly RESTful APIs
JavaScript on the Desktop
ES6 is Nigh
Real World Windows 8 Apps in JavaScript
Unit Testing for Great Justice

Recently uploaded (20)

PDF
gpt5_lecture_notes_comprehensive_20250812015547.pdf
PPTX
Spectroscopy.pptx food analysis technology
PPTX
Programs and apps: productivity, graphics, security and other tools
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PPTX
A Presentation on Artificial Intelligence
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPT
Teaching material agriculture food technology
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PDF
Encapsulation theory and applications.pdf
PDF
Assigned Numbers - 2025 - Bluetooth® Document
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
PPTX
Tartificialntelligence_presentation.pptx
PDF
Empathic Computing: Creating Shared Understanding
PDF
Unlocking AI with Model Context Protocol (MCP)
gpt5_lecture_notes_comprehensive_20250812015547.pdf
Spectroscopy.pptx food analysis technology
Programs and apps: productivity, graphics, security and other tools
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Advanced methodologies resolving dimensionality complications for autism neur...
MIND Revenue Release Quarter 2 2025 Press Release
A Presentation on Artificial Intelligence
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Teaching material agriculture food technology
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
Encapsulation theory and applications.pdf
Assigned Numbers - 2025 - Bluetooth® Document
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Encapsulation_ Review paper, used for researhc scholars
Per capita expenditure prediction using model stacking based on satellite ima...
Mobile App Security Testing_ A Comprehensive Guide.pdf
Accuracy of neural networks in brain wave diagnosis of schizophrenia
Tartificialntelligence_presentation.pptx
Empathic Computing: Creating Shared Understanding
Unlocking AI with Model Context Protocol (MCP)

Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patterns in JavaScript

  • 1. Callbacks, Promises, and Coroutines (oh my!) Asynchronous Programming Patterns in JavaScript Domenic Denicola http://domenicdenicola.com @domenicdenicola
  • 2. In non-web languages, most of the code we write is synchronous. aka blocking
  • 3. Console.WriteLine("What is your name?"); string name = Console.ReadLine(); Console.WriteLine("Hello, " + name);
  • 4. var fileNames = Directory.EnumerateFiles("C:"); foreach (var fileName in fileNames) { using (var f = File.Open(fileName, FileMode.Open)) { Console.WriteLine(fileName + " " + f.Length); } }
  • 5. using (var client = new WebClient()) { string html = client.DownloadString("http://news.ycombinator.com"); Console.WriteLine(html.Contains("Google")); Console.WriteLine(html.Contains("Microsoft")); Console.WriteLine(html.Contains("Apple")); }
  • 6. Thread.Start BackgroundWorker Control.InvokeRequired This often causes us some pain… … but hey, there’s always threads! Dispatcher.Invoke ThreadPool .AsParallel()
  • 7. Q: What are these threads doing, most of the time? A: waiting
  • 9. In JavaScript, we do things differently.
  • 10. There’s only one thread in JavaScript, so we use that thread to get stuff done.
  • 11. OK, let’s talk about… • The event loop • Callbacks • Promises • Coroutines
  • 13. You’ve seen event loops before:
  • 14. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
  • 15. this.btnOK.Click += this.btnOK_Click; private void btnOK_Click(object sender, EventArgs e) { // ... }
  • 16. $("#ok-button").click(function () { // ... }); setTimeout(function () { // ... }, 100); $.get("http://example.com", function (result) { // ... });
  • 17. Some event loop subtleties • Yielding • Async’s not sync • Errors • It’s not magic
  • 18. Event Loop Subtleties Yielding console.log("1"); $.get("/echo/2", function (result) { console.log(result); }); console.log("3"); // 1, 3, 2
  • 19. Event Loop Subtleties Async’s not sync var hi = null; $.get("/echo/hi", function (result) { hi = result; }); console.log(hi); // null
  • 20. Event Loop Subtleties Errors console.log("About to get the website..."); $.ajax("http://sometimesdown.example.com", { success: function (result) { console.log(result); }, error: function () { throw new Error("Error getting the website"); } }); console.log("Continuing about my business...");
  • 21. Event Loop Subtleties It’s not magic function fib(n) { return n < 2 ? 1 : fib(n-2) + fib(n-1); } console.log("1"); setTimeout(function () { console.log("2"); }, 100); fib(40); // 1 ... 15 seconds later ... 2 http://teddziuba.com/2011/10/node-js-is-cancer.html
  • 22. The event loop is tricky… but powerful.
  • 24. What we’ve seen so far has been doing asynchronicity through callbacks.
  • 25. Callbacks are OK for simple operations, but force us into continuation passing style.
  • 26. Recurring StackOverflow question: function getY() { var y; $.get("/gety", function (jsonData) { y = jsonData.y; }); return y; } Why doesn’t it work??? var x = 5; var y = getY(); console.log(x + y);
  • 27. After getting our data, we have to do everything else in a continuation:
  • 28. function getY(continueWith) { $.get("/gety", function (jsonData) { continueWith(jsonData.y); }); } var x = 5; getY(function (y) { console.log(x + y); });
  • 29. CPS Headaches • Doing things in sequence is hard • Doing things in parallel is harder • Errors get lost easily
  • 30. CPS Headaches Doing things in sequence is hard $("#button").click(function () { promptUserForTwitterHandle(function (handle) { twitter.getTweetsFor(handle, function (tweets) { ui.show(tweets); }); }); });
  • 31. CPS Headaches Doing things in parallel is harder var tweets, answers, checkins; twitter.getTweetsFor("domenicdenicola", function (result) { tweets = result; somethingFinished(); }); stackOverflow.getAnswersFor("Domenic", function (result) { answers = result; somethingFinished(); }); fourSquare.getCheckinsBy("Domenic", function (result) { checkins = result; somethingFinished(); });
  • 32. CPS Headaches Doing things in parallel is harder var finishedSoFar = 0; function somethingFinished() { if (++finishedSoFar === 3) { ui.show(tweets, answers, checkins); } }
  • 33. CPS Headaches Errors get lost easily function getTotalFileLengths(path, callback) { fs.readdir(path, function (err, fileNames) { var total = 0; var finishedSoFar = 0; function finished() { if (++finishedSoFar === fileNames.length) { callback(total); } } fileNames.forEach(function (fileName) { fs.readFile(fileName, function (err, file) { total += file.length; finished(); }); }); }); }
  • 34. You could write your own library to make this nicer…
  • 35. function parallel(actions, callback) { var results = []; function finished(result) { results.push(result); if (results.length === actions.length) { callback(results); } } actions.forEach(function (action) { action(finished); }); }
  • 36. parallel([ function (cb) { twitter.getTweetsFor("domenicdenicola", cb); }, function (cb) { stackOverflow.getAnswersFor("Domenic", cb); }, function (cb) { fourSquare.getCheckinsFor("Domenic", cb); } ], function (results) { console.log("tweets = ", results[0]); console.log("answers = ", results[1]); console.log("checkins = ", results[2]); });
  • 37. And in fact many people have: https://github.com/joyent/node/wiki/modules#wiki-async-flow
  • 38. The best of these (IMO) are based on an abstraction called “promises”
  • 40. Un-inverts the chain of responsibility: instead of calling a passed callback, return a promise.
  • 41. addWithCallback(a, b, function (result) { assert.equal(result, a + b); }); var promise = addWithPromise(a, b); promise.then(function (result) { assert.equal(result, a + b); });
  • 42. Why promises are awesome • Cleaner method signatures • Uniform return/error semantics • Easy composition • Easy sequential/parallel join • Always async • Exception-style error bubbling
  • 43. Promises are Awesome Cleaner method signatures Uniform return/error semantics $.get( url, [data], [success(data, status, xhr)], [dataType] )
  • 44. Promises are Awesome Cleaner method signatures Uniform return/error semantics $.ajax(url, settings) settings.success(data, status, xhr) settings.error(xhr, status, errorThrown) settings.complete(xhr, status)
  • 45. Promises are Awesome Cleaner method signatures Uniform return/error semantics fs.open( path, flags, [mode], [callback(error, file)] )
  • 46. Promises are Awesome Cleaner method signatures Uniform return/error semantics fs.write( file, buffer, offset, length, position, [callback(error, written, buffer)] )
  • 47. Promises are Awesome Cleaner method signatures Uniform return/error semantics getAsPromise(url, [data], [dataType]).then( function onFulfilled(result) { var data = result.data; var status = result.status; var xhr = result.xhr; }, function onBroken(error) { console.error("Couldn't get", error); } );
  • 48. Promises are Awesome Easy composition function getUser(userName, onSuccess, onError) { $.ajax("/user?" + userName, { success: onSuccess, error: onError }); }
  • 49. Promises are Awesome Easy composition function getUser(userName) { return getAsPromise("/user?" + userName); }
  • 50. Promises are Awesome Easy composition function getFirstName(userName, onSuccess, onError) { $.ajax("/user?" + userName, { success: function successProxy(data) { onSuccess(data.firstName); }, error: onError }); }
  • 51. Promises are Awesome Easy composition function getFirstName(userName) { return getAsPromise("/user?" + userName) .get("firstName"); }
  • 52. Promises are Awesome Easy sequential join $("#button").click(function () { promptUserForTwitterHandle(function (handle) { twitter.getTweetsFor(handle, function (tweets) { ui.show(tweets); }); }); });
  • 53. Promises are Awesome Easy sequential join $("#button").clickPromise() .then(promptUserForTwitterHandle) .then(twitter.getTweetsFor) .then(ui.show);
  • 54. Promises are Awesome Easy parallel join var tweets, answers, checkins; twitter.getTweetsFor("domenicdenicola", function (result) { tweets = result; somethingFinished(); }); stackOverflow.getAnswersFor("Domenic", function (result) { answers = result; somethingFinished(); }); fourSquare.getCheckinsBy("Domenic", function (result) { checkins = result; somethingFinished(); });
  • 55. Promises are Awesome Easy parallel join Q.all([ twitter.getTweetsFor("domenicdenicola"), stackOverflow.getAnswersFor("Domenic"), fourSquare.getCheckinsBy("Domenic") ]).then(function (results) { console.log(results[0], results[1], results[2]); });
  • 56. Promises are Awesome Easy parallel join Q.all([ twitter.getTweetsFor("domenicdenicola"), stackOverflow.getAnswersFor("Domenic"), fourSquare.getCheckinsBy("Domenic") ]).spread(function (tweets, answers, checkins) { console.log(tweets, answers, checkins); });
  • 57. Promises are Awesome Always async function getUser(userName, onSuccess, onError) { if (cache.has(userName)) { onSuccess(cache.get(userName)); } else { $.ajax("/user?" + userName, { success: onSuccess, error: onError }); } }
  • 58. Promises are Awesome Always async console.log("1"); getUser("ddenicola", function (user) { console.log(user.firstName); }); console.log("2"); // 1, 2, Domenic
  • 59. Promises are Awesome Always async console.log("1"); getUser("ddenicola", function (user) { console.log(user.firstName); }); console.log("2"); // 1, Domenic, 2
  • 60. Promises are Awesome Always async function getUser(userName) { if (cache.has(userName)) { return Q.ref(cache.get(userName)); } else { return getWithPromise("/user?" + userName); } }
  • 61. Promises are Awesome Always async console.log("1"); getUser("ddenicola").then(function (user) { console.log(user.firstName); }); console.log("2"); // 1, 2, Domenic (every time)
  • 62. Promises are Awesome Exception-style error bubbling getUser("Domenic", function (user) { getBestFriend(user, function (friend) { ui.showBestFriend(friend); }); });
  • 63. Promises are Awesome Exception-style error bubbling getUser("Domenic", function (err, user) { if (err) { ui.error(err); } else { getBestFriend(user, function (err, friend) { if (err) { ui.error(err); } else { ui.showBestFriend(friend); } }); } });
  • 64. Promises are Awesome Exception-style error bubbling getUser("Domenic") .then(getBestFriend, ui.error) .then(ui.showBestFriend, ui.error);
  • 65. Promises are Awesome Exception-style error bubbling getUser("Domenic") .then(getBestFriend) .then(ui.showBestFriend, ui.error);
  • 66. Promises are Awesome Exception-style error bubbling ui.startSpinner(); getUser("Domenic") .then(getBestFriend) .then( function (friend) { ui.showBestFriend(friend); ui.stopSpinner(); }, function (error) { ui.error(error); ui.stopSpinner(); } );
  • 67. Promises are Awesome Exception-style error bubbling ui.startSpinner(); getUser("Domenic") .then(getBestFriend) .then(ui.showBestFriend, ui.error) .fin(ui.stopSpinner);
  • 68. Promises are Awesome Exception-style error bubbling function getBestFriendAndDontGiveUp(user) { return getUser(user).then( getBestFriend, function (error) { if (error instanceof TemporaryNetworkError) { console.log("Retrying after error", error); return getBestFriendAndDontGiveUp(user); } throw error; }); }
  • 69. Sounds great. How do I get in on this action?
  • 70. Use Q • By Kris Kowal, @kriskowal • https://github.com/kriskowal/q • Can consume promises from jQuery etc. • Implements various CommonJS standards
  • 71. If you’re already using jQuery’s promises, switch to Q: https://github.com/kriskowal/q/wiki/jQuery
  • 73. Creating promises with Q Fulfilling promises // We have: setTimeout(doSomething, 1000); // We want: delay(1000).then(doSomething);
  • 74. Creating promises with Q Fulfilling promises function delay(ms) { var deferred = Q.defer(); setTimeout(deferred.resolve, ms); return deferred.promise; } delay(1000).then(doSomething);
  • 75. Creating promises with Q Breaking promises function getWithTimeout(url, ms, onSuccess, onError) { var isTimedOut = false, isHttpErrored = false; setTimeout(function () { if (!isHttpErrored) { isTimedOut = true; onError(new Error("timed out")); } }, ms); $.ajax(url, { success: function (result) { if (!isTimedOut) { onSuccess(result); } }, error: function (xhr, status, error) { if (!isTimedOut) { isHttpErrored = true; onError(error); } } }); }
  • 76. Creating promises with Q Breaking promises function getWithTimeout(url, ms) { var deferred = Q.defer(); setTimeout(function () { deferred.reject(new Error("timed out")); }, ms); $.ajax(url, { success: deferred.resolve, error: deferred.reject }); return deferred.promise; }
  • 77. Creating promises with Q Building abstractions function timeout(promise, ms) { var deferred = Q.defer(); promise.then(deferred.resolve, deferred.reject); setTimeout(function () { deferred.reject(new Error("timed out")); }, ms); return deferred.promise; } function getWithTimeout(url, ms) { return timeout(getAsPromise(url), ms); }
  • 79. They clean up our method signatures. They’re composable, they’re joinable, and they’re dependably async. They unify various callback conventions into something very much like return values and exceptions.
  • 80. But… we still have to write in CPS.
  • 82. “Coroutines are computer program components that generalize subroutines to allow multiple entry points for suspending and resuming execution at certain locations.” http://en.wikipedia.org/wiki/Coroutine
  • 83. WTF!?
  • 84. Nice: var xP = getX(); var yP = getY(); var zP = getZ(); Q.all([xP, yP, zP]).spread(function (x, y, z) { console.log(x + y + z); });
  • 85. Nicer: var [x, y, z] = await Q.all([getX(), getY(), getZ()]); console.log(x + y + z);
  • 86. Nice: $("#button").clickPromise() .then(promptUserForTwitterHandle) .then(twitter.getTweetsFor) .then(ui.show);
  • 87. Nicer: await $("#button").clickPromise(); var handle = await promptUserForTwitterHandle(); var tweets = await twitter.getTweetsFor(handle); ui.show(tweets);
  • 88. Q: Can’t the compiler do this for me? A: yes… if you are willing to introduce a compiler.
  • 89. Several options, none perfect • Kaffeine: http://weepy.github.com/kaffeine/ • Traceur: http://tinyurl.com/traceur-js • TameJS: http://tamejs.org/ • Node fork: http://tinyurl.com/node-async
  • 90. Q: OK well… can’t the interpreter do this for me? A: yes… if you’re willing to wait for the next version of JS.
  • 91. The next version of JavaScript (“ECMAScript Harmony”) has a limited form of coroutines that can be twisted to do something like what we want.
  • 92. ECMAScript Harmony generators function* fibonacci() { var [prev, curr] = [0, 1]; for (;;) { [prev, curr] = [curr, prev + curr]; yield curr; } } for (n of fibonnaci()) { console.log(n); } http://wiki.ecmascript.org/doku.php?id=harmony:generators
  • 93. ECMAScript Harmony generators var eventualAdd = Q.async(function* (pA, pB) { var a = yield pA; var b = yield pB; return a + b; }); https://github.com/kriskowal/q/tree/master/examples/async-generators
  • 94. ECMAScript Harmony generators // Can only use yield as we want to within // Q.async'ed generator functions Q.async(function* () { // Talk to the server to get one and two. var three = yield eventualAdd(getOne(), getTwo()); assert.equal(three, 3); })(); https://groups.google.com/d/topic/q-continuum/7PWKbgeFA48/discussion
  • 95. ECMAScript Harmony generators // Given promise-returning delay(ms) as before: var animateAsync = Q.async(function* (el) { for (var i = 0; i < 100; ++i) { element.style.left = i; yield delay(20); } }); http://wiki.ecmascript.org/doku.php?id=strawman:async_functions
  • 96. ECMAScript Harmony generators Q.async(function* () { var el = document.getElementById("my-element"); yield animateAsync(el); console.log("it's done animating"); })(); https://groups.google.com/d/topic/q-continuum/7PWKbgeFA48/discussion
  • 97. So coroutines are a bit of a mess, but we’ll see how things shape up.
  • 98. Recap • Async is here to stay • But you don’t have to dive into callback hell • Use promises • Use Q • Maybe use coroutines if you’re feeling brave

Editor's Notes

  • #42: Decouples the action from the subsequent use of the result.