Async Programming

preventing callback hell in JS

Simple App Database process

  • Starting Node Server
  • Connecting to Database
  • Remove all Test Users
  • Add new Test Users
  • View all Test users

Keep the code shallow

  • non-anonymous callbacks
  • promise interface (catching chained params)
  • 3rd party promise libs
  • rxjs streams
  • ES6 async / await

Non-anonymous callbacks

import {Meteor} from 'meteor/meteor';
let mongoose = require('mongoose');
let userSchema, User, db;

/** ACTION FLOWS FROM TOP TO BOTTOM **/

/* CALLBACK #1 */
function meteorStartedUp() {

    mongoose.connect('mongodb://localhost:3001/local');
    userSchema = mongoose.Schema({
        name: String
    });
    User = mongoose.model('users', userSchema);
    db = mongoose.connection;
    db.on('error', console.error.bind(console, 'connection error:'));

    db.once('open', mongooseDBOpened);
}

/* CALLBACK #2 */
function mongooseDBOpened() {
    User.remove({}, allUsersRemoved);
}

/* CALLBACK #3 */
function allUsersRemoved(err, users) {
    if (err) {
        throw new Error(err);
    }
    let theuy = new User({name: "Theuy Limpanont"});
    let hans = new User({name: "Hans Kramer"});
    User.collection.insert([theuy, hans], testUsersInserted);
}

/* CALLBACK #4 */
function testUsersInserted(err, docs) {
    if (err) {
        throw new Error(err);
    }
    User.find({}, allUsersFound);
}

/* CALLBACK #5 */
function allUsersFound(err, users) {
    if (err) {
        throw new Error(err);
    }
    console.log(users);
}
/** END FLOWS FROM TOP TO BOTTOM **/

Meteor.startup(meteorStartedUp);

Promise interface

import {Meteor} from 'meteor/meteor';
let mongoose = require('mongoose');
let userSchema, User, db;

function connectToMongooseDB() {
    return new Promise(function (resolve, reject) {
        mongoose.connect('mongodb://localhost:3001/local');
        userSchema = mongoose.Schema({
            name: String
        });
        User = mongoose.model('users', userSchema);
        db = mongoose.connection;
        db.on('error', function (error) {
            reject(error);
        });
        db.once('open', function () {
            resolve(db);
        });
    });
}

function removeAllUsers() {
    return new Promise(function (resolve, reject) {
        User.remove({}, function (err, users) {
            if (err) {
                reject(err);
            }
            resolve(users)
        });
    });
}

function insertTestUsers() {
    return new Promise(function (resolve, reject) {
        let theuy = new User({name: "Theuy Limpanont"});
        let hans = new User({name: "Hans Kramer"});
        User.collection.insert([theuy, hans], function (err, result) {
            if (err) {
                reject(err);
            }
            resolve(result)
        });
    });
}

function findAllUsers() {
    return new Promise(function (resolve, reject) {
        User.find({}, function (err, users) {
            if (err) {
                reject(err);
            }
            resolve(users)
        });
    });
}

function meteorStartUp() {
    return new Promise(function(resolve, reject) {
        Meteor.startup(function () {
            resolve();
        });
    });
}

/** ACTION FLOWS FROM TOP TO BOTTOM **/
meteorStartUp()
    .then(connectToMongooseDB)
    .then(removeAllUsers)
    .then(insertTestUsers)
    .then(findAllUsers)
    .then(function(users) {
        console.log(users);
    })
    .catch(function(error) {
        console.error("error from promise catch: ", error);
    });
/** END FLOWS FROM TOP TO BOTTOM **/

Third party Promise lib

import {Meteor} from 'meteor/meteor';
let mongoose = require('mongoose');
/**
 * The node function should conform to node.js convention of accepting a callback as last argument and calling that callback
 * with error as the first argument and success value on the second argument.
 */
let Promise = require('bluebird');
let userSchema, User, db;

function connectToMongooseDB() {
    return new Promise(function (resolve, reject) {
        mongoose.connect('mongodb://localhost:3001/local');
        userSchema = mongoose.Schema({
            name: String
        });
        User = mongoose.model('users', userSchema);
        db = mongoose.connection;
        db.on('error', function (error) {
            reject(error);
        });
        db.once('open', function () {
            resolve(db);
        });
    });
}

function removeAllUsers() {
    return Promise.promisify(User.remove)
        .call(User, {});
}

function insertTestUsers() {
    let theuy = new User({name: "Theuy Limpanont"});
    let hans = new User({name: "Hans Kramer"});
    return Promise.promisify(User.collection.insert)
        .call(User.collection, [theuy, hans]);
}

function findAllUsers() {
    return Promise.promisify(User.find)
        .call(User, {});
}

function meteorStartUp() {
    return new Promise(function(resolve, reject) {
        Meteor.startup(function () {
            resolve();
        });
    });
}

/** ACTION FLOWS FROM TOP TO BOTTOM **/
meteorStartUp()
    .then(connectToMongooseDB)
    .then(removeAllUsers)
    .then(insertTestUsers)
    .then(findAllUsers)
    .then(function(users) {
        console.log(users);
    })
    .catch(function(error) {
        console.error("error from promise catch: ", error);
    });
/** END FLOWS FROM TOP TO BOTTOM **/

Catch all params of promises

import {Meteor} from 'meteor/meteor';
let mongoose = require('mongoose');
let userSchema = mongoose.Schema({name: String});
let User = mongoose.model('users', userSchema);

function connectToMongooseDB() {
    return new Promise(function (resolve, reject) {
        mongoose.connect('mongodb://localhost:3001/local');
        let db = mongoose.connection;
        db.on('error', function (error) {
            reject(error);
        });
        db.once('open', function () {
            resolve(db);
        });
    });
}

function removeAllUsers() {
    return new Promise(function (resolve, reject) {
        User.remove({}, function (err, users) {
            if (err) {
                reject(err);
            }
            resolve(users)
        });
    });
}

function insertTestUsers() {
    return new Promise(function (resolve, reject) {
        let theuy = new User({name: "Theuy Limpanont"});
        let hans = new User({name: "Hans Kramer"});
        User.collection.insert([theuy, hans], function (err, result) {
            if (err) {
                reject(err);
            }
            resolve(result)
        });
    });
}

function findAllUsers() {
    return new Promise(function (resolve, reject) {
        User.find({}, function (err, users) {
            if (err) {
                reject(err);
            }
            resolve(users)
        });
    });
}

function meteorStartUp() {
    return new Promise(function (resolve, reject) {
        Meteor.startup(function () {
            resolve();
        });
    });
}

/** ACTION FLOWS FROM TOP TO BOTTOM **/
Promise.all([
    meteorStartUp(),
    connectToMongooseDB(),
    removeAllUsers(),
    insertTestUsers(),
    findAllUsers()
])
    .then(function ([meteorStartup, db, removeUsers, insertedUsers, allUsers]) {
        console.log("dbHost: ", db.host);
        console.log("dbPort: ", db.port);
        console.log("allUsers: ", allUsers);
    })
    .catch(function (error) {
        console.error("error from promise catch: ", error);
    });
/** END FLOWS FROM TOP TO BOTTOM **/

RxJS Observable streams

import {Meteor} from 'meteor/meteor';
import Rx from 'rxjs/Rx';
let mongoose = require('mongoose');
let userSchema = mongoose.Schema({name: String});
let User = mongoose.model('users', userSchema);

let connectToMongooseDB$ = new Rx.Observable(function (observer) {
    mongoose.connect('mongodb://localhost:3001/local');
    let db = mongoose.connection;
    db.on('error', function (err) {
        observer.error(err);
    });
    db.once('open', function () {
        observer.next(db);
    });
});

let removeAllUsers$ = new Rx.Observable(function (observer) {
    User.remove({}, function (err, users) {
        if (err) {
            observer.error(err);
        }
        observer.next(users);
    });
});


let insertTestUsers$ = new Rx.Observable(function (observer) {
    let theuy = new User({name: "Theuy Limpanont"});
    let hans = new User({name: "Hans Kramer"});
    User.collection.insert([theuy, hans], function (err, result) {
        if (err) {
            observer.error(err);
        }
        observer.next(result);
    });
});


let findAllUsers$ = new Rx.Observable(function (observer) {
    User.find({}, function (err, users) {
        if (err) {
            observer.error(err);
        }
        observer.next(users);
    });
});

let meteorStartUp$ = new Rx.Observable(function (observer) {
    Meteor.startup(function () {
        observer.next();
    });
});


/** ACTION FLOWS FROM TOP TO BOTTOM **/
Rx.Observable.combineLatest(
    meteorStartUp$,
    connectToMongooseDB$,
    removeAllUsers$,
    insertTestUsers$,
    findAllUsers$
)
    .subscribe(function ([meteorStartup, db, removeUsers, insertedUsers, allUsers]) {
        console.log("dbHost: ", db.host);
        console.log("dbPort: ", db.port);
        console.log("allUsers: ", allUsers);
    }, function (error) {
        console.error("error from observable catch: ", error);
    }, function () {
        console.log("stream completed!");
    });
/** END FLOWS FROM TOP TO BOTTOM **/

ES6 Async / Await

import {Meteor} from 'meteor/meteor';
let mongoose = require('mongoose');
let userSchema, User, db;

function connectToMongooseDB() {
    return new Promise(function (resolve, reject) {
        mongoose.connect('mongodb://localhost:3001/local');
        userSchema = mongoose.Schema({
            name: String
        });
        User = mongoose.model('users', userSchema);
        db = mongoose.connection;
        db.on('error', function (error) {
            reject(error);
        });
        db.once('open', function () {
            resolve(db);
        });
    });
}

function removeAllUsers() {
    return new Promise(function (resolve, reject) {
        User.remove({}, function (err, users) {
            if (err) {
                reject(err);
            }
            resolve(users)
        });
    });
}

function insertTestUsers() {
    return new Promise(function (resolve, reject) {
        let theuy = new User({name: "Theuy Limpanont"});
        let hans = new User({name: "Hans Kramer"});
        User.collection.insert([theuy, hans], function (err, result) {
            if (err) {
                reject(err);
            }
            resolve(result)
        });
    });
}

function findAllUsers() {
    return new Promise(function (resolve, reject) {
        User.find({}, function (err, users) {
            if (err) {
                reject(err);
            }
            resolve(users)
        });
    });
}

function meteorStartUp() {
    return new Promise(function(resolve, reject) {
        Meteor.startup(function () {
            resolve();
        });
    });
}

/** ACTION FLOWS FROM TOP TO BOTTOM **/
(async function() {
    await meteorStartUp();
    await connectToMongooseDB();
    await removeAllUsers();
    let insertedUsers = await insertTestUsers();
    console.log("insertedIds: ", insertedUsers.insertedIds);
    let allUsers = await findAllUsers();
    console.log("all users: " + JSON.stringify(allUsers, null, 4));
})();

/** END FLOWS FROM TOP TO BOTTOM **/

Example Meteor Application

Pub Sub messaging

DEMO TIME

Made with Slides.com