Asynchronous Programming with JavaScript

18
<web/F> <web/F> Taming the Async Beast By Niloy Mondal @niloy_mondal84

Transcript of Asynchronous Programming with JavaScript

<web/F><web/F>

Taming the Async BeastBy Niloy Mondal

@niloy_mondal84

<web/F><web/F>

Background

As a JS developer, async programming is a part of life.

Examples of Async APIs:

setTimeout, setInterval, addEventListener

XMLHttpRequest

CSS Animations

Database transactions in NodeJS

But no good tools to do async programming… till now

<web/F><web/F>

Callback Hell

Lets say we want to create a new user, upload photo and finally fetch all details of the user.

createUser(userDetails, function(response) {

if (response.success) {

var user = response.user;

uploadPhoto(user.id, photo, function(response) {

if (response.success) {

getUser(user.id, function(response) {...});

} else {

alert("Error: cannot upload photo");

}

});

} else {

alert("Error: cannot create user");

}

});

<web/F><web/F>

Problems

<web/F><web/F>

PromiseRules are meant to broken, Promises are meant to be resolved.

Welcome `Promises` aka `Futures` aka `Continuation Monad` from functional programming.

Many initial implementations but now standardized. Now part of JS.

function setTimeoutP(delay) {

return new Promise(function(resolve, reject) {

setTimeout(resolve, delay);

});

}

setTimeoutP(2000).then(function() {

console.log("After 2 seconds");

});

<web/F><web/F>

Taming the Async Beast (Attempt 1)

var userId;

createUser(userDetails)

.then(function(user) {

userId = user.id;

return uploadPhoto(userId);

}).then(function() {

return getUser(userId);

}).then(function(userDetails) {

// user details fetched

}).catch(function() {

alert("Oops! Error occoured");

});

<web/F><web/F>

Benefits of using Promise

No pyramid of doom anymore, code is indented only to 1 level.

Code is somewhat sequential.

Execution flows from one `then` to another, top to bottom.

Clean error handling using `catch` similar to `try...catch` block.

<web/F><web/F>

Parallel execution using Promises

Usecase: Edit User Information page

var userDetailsPromise = getUser(userId);

var occupationValuesPromise = getOccupationValues();

Promise.all([userDetailsPromise, occupationValuesPromise])

.then(function(args) {

var userDetail = args[0];

var occupationValues = args[1];

// fill the UI elements here

});

<web/F><web/F>

Problems with Promise

Does solve the async problem to some extent but it still feels like a workaround/hack.

We have keep writing these `then` over and over for each async call.

If..else type conditional flow is hard.

For some complicated use cases, even Promises become an unreadable mess.

<web/F><web/F>

Can we do better?

.

<web/F><web/F>

Small introduction to Generators

What will be the output of the following code?

function* squares() {

var i = 1;

while(true) {

yield i * i;

i++;

}

}

var n = squares();

console.log(n.next().value);

console.log(n.next().value);

console.log(n.next().value);

<web/F><web/F>

Taming the Async Beast (Attempt 2)

Lets create a user, upload photo and fetch all details.

spawn(function*() {

try {

var user = yield createUser(userDetails);

yield uploadPhoto(user.id);

var userDetails = yield getUser(user.id);

// user details fetched

} catch(ex) {

alert("Oops! Error occoured");

}

});

<web/F><web/F>

Things to remember

• `spawn` function is a library code (http://taskjs.org/)• Code is sequential even though we are doing async operations• `try… catch` just works• Requires `Promise` to work.The functions `createUser` must return a _Promise_ for this pattern to work.

The general rule of thumb is that `yield` can be used infront of functions that return Promise.

<web/F><web/F>

Parallel execution

spawn(function*() {

var values = yield [getUser(userId), getOccupationValues()];

var userDetailsPromise = values[0];

var occupationValuesPromise = values[1];

});

The way to think about this is, whatever you can pass to `Promise.all` can be passed to `yield`.

<web/F><web/F>

Serial Execution of Async Task (Unknown length)

Say you have a CSV file that you read line by line, extract values from each line and upload information by firing an API. The execution of each API needs to be serial (one after another) because the data below depends on the data above it.

spawn(function*() {

var lines = fs.readFileSync("foo.csv", "utf-8").split("\n");

for (var line of lines) {

yield pushRow(line);

}

});

<web/F><web/F>

Using Generators today

Generators are natively implemented in

• Chrome/Opera• Firefox• NodeJS(with harmony flag)

For other browsers, various transpilers can be used like Traceur or Babel. I personally use Tracuer.

<web/F><web/F>

Thank you

<web/F><web/F>

Twitter: niloy_mondal84Github: https://github.com/niloyBlog: https://github.com/niloy/blog/issues