Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patterns in JavaScript
This document discusses asynchronous programming patterns in JavaScript, focusing on the differences between synchronous and asynchronous execution using callbacks, promises, and coroutines. It highlights the complexity introduced by callbacks and advocates for the use of promises to simplify error handling and composition of asynchronous operations. Additionally, it touches on the future of JavaScript with coroutines and ECMAScript Harmony, which aim to improve the handling of asynchronous code.
Introduces asynchronous programming, focusing on patterns like callbacks, promises, and coroutines in JavaScript, contrasting with synchronous code in non-web languages.
Explains the event loop concept in JavaScript, including nuances of yielding, async behavior, and common pitfalls of event-driven programming.
Details on using callbacks for asynchronous processing, discussing challenges like continuation-passing style (CPS) and difficulties in sequencing and handling errors.
Discusses promises as an advancement over callbacks, highlighting their benefits such as cleaner syntax, better error handling, and ease of composition in asynchronous code.
Examines the concept of coroutines, proposing them as a elegant solution to manage asynchronous programming flow, alongside ECMAScript Harmony generative support.
Summarizes the main points about asynchronous programming, emphasizing the use of promises and coroutines in modern JavaScript development.
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 arethese threads doing, most of the time? A: waiting
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
What we’ve seenso far has been doing asynchronicity through callbacks.
25.
Callbacks are OKfor simple operations, but force us into continuation passing style.
26.
Recurring StackOverflow question: functiongetY() { 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 ourdata, 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 • Doingthings 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(); }); }); }); }
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(); });
They clean upour 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.
“Coroutines are computerprogram components that generalize subroutines to allow multiple entry points for suspending and resuming execution at certain locations.” http://en.wikipedia.org/wiki/Coroutine
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 versionof 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 area 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