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();
});
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 alreadyusing jQuery’s promises, switch to Q:
https://github.com/kriskowal/q/wiki/jQuery
73.
Creating promises withQ
Fulfilling promises
// We have:
setTimeout(doSomething, 1000);
// We want:
delay(1000).then(doSomething);
74.
Creating promises withQ
Fulfilling promises
function delay(ms) {
var deferred = Q.defer();
setTimeout(deferred.resolve, ms);
return deferred.promise;
}
delay(1000).then(doSomething);
75.
Creating promises withQ
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);
}
}
});
}
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