Async Programming with JavaScript

How to Survive in Async Hell

Song YANG

Sync Programming Is Intuitive

 
var x = getX(); // 1
var y = getY(); // 5

var sum = x + y;

console.log(sum); // 6
$.ajaxSetup({
  async: false,
  timeout: 1000
})

var foo = $.get('//google.com')
var bar = $.get('//stackoverflow.com')
var qux = $.get('//bing.com')

console.log(foo)
console.log(bar)
console.log(qux)

Intuitive Data Requesting?

What's Wrong?

 

Reality IsWhat We Called Reality

JavaScript Is Just A Single-Threaded Event Loop

How We Handle That?

 

Async Programming

 
  • Callbacks

  • Promises

  • Event Emitter

  • Generators / yield

  • Async / await

Async Approaches

Callbacks

 
function B(callback) {
  // Do operation that takes some time
  callback('Done!');
}

function A(message) {
  console.log(message);
}

// Execute `B` with `A` as a callback
B(A);

Functions are the first-class citizens of the language

 

They can be passed around like any other variable to other functions

Callbacks

Error-first callbacks

 
fs.readFile('/etc/passwd', (err, data) => {
  if (err) throw err;
  console.log(data);
});

 Heart of Node.js itself - the core modules are using it as well as most of the modules found on NPM.

 

Challenges

  • It is easy to build callback hells or spaghetti code with them if not used properly

  • Callbacks can run more than once

  • Can't return values with the return statement, nor can use the throw keyword

Callback Hell

 
getData(function(a){  
    getMoreData(a, function(b){
        getMoreData(b, function(c){ 
            getMoreData(c, function(d){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

Callback Run More Than Once

function printRandomId(callback) {
  setTimeout(() => {
    callback(Math.random())
    callback(Math.random())
  }, 0)
}

printRandomId((val) => {
  console.log(val)
})




// print twice
function getY () {
  var innerY

  setTimeout(function () {
    innerY = 'Y'
  }, 0)

  return innerY
}

var y = getY();

console.log(y);


// undefined

Cannot Return Value

 

try/catch Doesn't Work

function throwUncaughtError () {
  try {
    setTimeout(function() {
      throw new Error('Whoops!');
    }, 1000);
  }
  catch (e) {
    alert("You won't see this!");
  }
}

Promises

 

an alternative way to manage your  asynchronous code in JavaScript

 

 BASICS

promise  represents the eventual result of an asynchronous operation

[ Promises/A+​]

What are promises?

 BASICS

Pending: initial state, not fulfilled or rejected.

Fulfilled: meaning that the operation completed successfully.

Rejected: meaning that the operation failed.

 

Settled:  once a promise is settled, it is immutable and cannot be changed.

Promises States

 BASICS

 

Standard Libraries

Q.js

When.js

RSVP.js

thenable, jqXHR Object  

Ⅱ USAGES

 

Using Promises

Ⅱ USAGES

 

Creating Promises

var somePromise = new Promise(function(resolve, reject) {
    // 异步处理
    // 处理结束后、调用resolve 或 reject
    setTimeout(function() {
      resolve('Value Resolved')
    }, 1000);
});

Ⅱ USAGES

 

Creating Promises

new Promise(function(resolve){
    resolve(42);
});

// Returns a Promise object that is resolved with the given value.
Promise.resolve(42)

Promise.resolve, returns a Promise object that is resolved with the given value.

Ⅱ USAGES - Promise.prototype

 

then

promise.then(onFulfilled, onRejected)

resolve(success)

     onFulfilled will be called after promise is fulfilled

reject(failed)

     onRejected will be called after promise is rejected

Both onFulfilled onRejected  are optional

Ⅱ USAGES - Promise.prototype

then

Ⅱ USAGES - Promise.prototype

then

somePromise().then(function () {
  // I'm inside a then() function!
});
  1. return another promise

  2. return a synchronous value (or undefined)

  3. throw a synchronous error

Every promise gives you a then() method,

or catch(), which is just sugar for then(null, ...)

Ⅱ USAGES

 

Real Case

getUser('userID')
    .then(onFulfilled, onRejected)

getUser('userID')
    .then(function(userData) {
        if(userData) return 'Success!'
        else throw new Error('No user data was found...') 
        // your app logic error, always throw with an Error...
    }, function(reason){
        //handle DB errors...
    })

always return or throw from inside a then() function

Ⅱ USAGES

 

Error Handling

getUser('userID')
    .then(null, onRejected)
getUser('userID')
    .catch(onRejected)

Ⅱ USAGES

 

Chaining Promises

getUser('userID')
  .then(onFulfillOne, onRejectOne)
  .then(onFulfillTwo)
  .then(null, onRejectTwo)

getUser('userID')
  .then(updateOtherthing)
  .then(deleteStuff)  
  .then(logResults)
  .catch(handleError)

" a promise handler always return another Promise"

Therefore it can be chained to other Promises

Ⅲ Advanced

Chains' Order

function taskA() {
  console.log("Task A");
}
function taskB() {
  console.log("Task B");
}
function onRejected(error) {
  console.log("Catch Error: A or B", error);
}
function finalTask() {
  console.log("Final Task");
}

var promise = Promise.resolve();
promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
  .then(finalTask);

Ⅲ Advanced

Execution Order

Ⅲ Advanced

Advanced Mistakes

// case 1
login().then(function () {
  return getUserInfo()
})
.then(finalHandler)
.catch(errorHandler)

// case 2
login().then(function (token) {
  getUserInfo(token)
})
.then(finalHandler)
.catch(errorHandler)

// case 3
login().then(getUserInfo())
.then(finalHandler)
.catch(errorHandler)

// case 4
login().then(getUserInfo)
.then(finalHandler)
.catch(errorHandler)


Ⅲ Advanced

 

Puzzle #0

login().then((token) => {
  return getUserInfo(token)
})
.then(finalHandler)
.catch(errorHandler)

login
|-----------|
            getUserInfo(token)
            |-----------|
                        finalHandler(userInfo)
                        |------------------|

Ⅲ Advanced

 

Puzzle #1

login().then(function () {
  return getUserInfo()
})
.then(finalHandler)
.catch(errorHandler)

login
|-----------|
            getUserInfo(undefined)
            |-----------|
                        errorHandler(rejectedUserInfo)
                        |------------------|

Ⅲ Advanced

 

Puzzle #2

login().then(function (token) {
  getUserInfo(token)
})
.then(finalHandler)
.catch(errorHandler)

login
|-----------|
            getUserInfo(token)
            |-----------|
            finalHandler(undefined)
            |------------------|

Ⅲ Advanced

Puzzle #3

login().then(getUserInfo())
.then(finalHandler)
.catch(errorHandler)

login
|-----------|
getUserInfo(undefined)
|---------------------|
            finalHandler(token)
            |------------------|

Ⅲ Advanced

 

Puzzle #4

login().then(getUserInfo)
.then(finalHandler)
.catch(errorHandler)

login
|-----------|
            getUserInfo(token)
            |-----------------|
                              finalHandler(userInfo)
                              |------------------|

Tacit programming, Point Free Style

Ⅲ Advanced

 

Puzzle Code

Ⅲ Advanced

 

Async Flow

Defined in ES6

Promise.all(iterable)

Promise.race(iterable)

Other Libraries

Promise.each(iterable)

Promise.map(iterable)

Promise.map(iterable)

...

Generators / yield

 

Generators

 

What Is the Generators

 
Not signed in

Generators are functions that can be paused and resumed

 
function* foo () {  
  var index = 0;
  while (index < 2) {
    yield index++;
  }
}
var bar =  foo();

console.log(bar.next());    // { value: 0, done: false }  
console.log(bar.next());    // { value: 1, done: false }  
console.log(bar.next());    // { value: undefined, done: true }  

Co

Generator Based Lib

Co, The ultimate generator based flow-control goodness for nodejs

co(function *(){
  // resolve multiple promises in parallel
  var a = Promise.resolve(1);
  var b = Promise.resolve(2);
  var c = Promise.resolve(3);
  var res = yield [a, b, c];
  console.log(res);
  // => [1, 2, 3]
}).catch(onerror);

Koa

Expressive Middleware

Koa, expressive middleware for node.js using generators

var koa = require('koa');
var app = koa();

// logger

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});

// response

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

Async / await

 

Async/await

Async functions

Under the hood async functions using Promises, it will return with a Promise


async function foo () {
    var message = await operationTakesTime();
    console.log(message);
}

Async/await

Async functions

function promisingOperation() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            if( Math.round(Math.random()) )
                resolve('Success!');
            else
                reject('Failure!');
        }, 1000);
    })
}
 
async function foo() {
    var message = await promisingOperation();
    console.log(message);
}

window.foo = foo

Async/await

await vs yield

Using yield for async is kind of hack, it's meant for lazy sequences and iterators.

await separates out the concerns by allowing yield to be used for its original purpose, and await for asynchronicity.

Async/await

Koa supports await

// Koa application is now a class and requires the new operator.
const app = new Koa();

// uses async arrow functions
app.use(async (ctx, next) => {
  try {
    await next(); // next is now a function
  } catch (err) {
    ctx.body = { message: err.message };
    ctx.status = err.status || 500;
  }
});

app.use(async ctx => {
  const user = await User.getById(ctx.session.userid); // await instead of yield
  ctx.body = user; // ctx instead of this
});

Which One Do you Prefer?

Async Programming with JavaScript

By Owen Yang

Async Programming with JavaScript

  • 1,402