Three Men in a Boat
(To Say Nothing of the Callback)

Zhigalov Sergey, Yandex

Three Men in a Boat
(To Say Nothing of the Callback)

Zhigalov Sergey

Frontend Union Conf, 2015

Flights to St. Petersburg

  1. Get list of airlines
  2. Get flight cost
  3. Sort airlines by cost in ascending order

Callback

        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            Get list of airlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    Get flight cost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = Sort airlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            Get list of airlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    Get flight cost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = Sort airlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err);  }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, unpaidairlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
        function getFlights(cb) {
            getAirlines(function (err, airlines) {
                if (err) { return cb(err); }
                var flightsCost = [];
                var airlinesCounter = airlines.length;
                airlines.forEach(function (airline, index) {
                    getCost(airline, function (err, cost) {
                        if (err) { return cb(err); }
                        flightsCost[index] = cost;
                        if (--airlinesCounter === 0) {
                            var sortedAirlines = sortAirlines(airlines, flightsCost);
                            cb(null, sortedAirlines);
                        }
                    })
                });
            });
        }
    
Criteria Callback
overheads no
level of nesting 5
visual noise yes
separation of data and error no
code duplication yes
missed error yes
linear code no

Promises

Promises

        myPromise()
            .then(function onSuccess() { return data; },
                  function onRejected() { ... })
            .then(function(data) { ... })
         
    

Promises

        myPromise()
            .then(function onSuccess() { return data; },
                  function onRejected() { ... })
            .then(function(data) { ... })
         
    
promise

Promises

        myPromise()
            .then(function onSuccess() { return data; },
                  function onRejected() { ... })
            .then(function(data) { ... })
         
    
promise

Promises

        myPromise()
            .then(function onSuccess() { return data; },
                  function onRejected() { ... })
            .then(function(data) { ... })
         
    
promise

Promises/A+

Михаил Давыдов "Promise – это не больно"

library time(ms) memory(MB)
callbacks 211 25.57

bluebird

389 53.49

RSVP.js

785 108.14

Vow

798 102.08

when.js

851 60.46

lie

1065 187.69

Davy Jones

1298 135.43

then promise

2438 338.91

* results for 10000 parallel executions, 1 ms per I/O op (Original)

Promises. Bluebird

        function myFunction(arg1, arg2, cb) {
            cb(err, result);
        }
        var myPromisifyFunction = Promise.promisify(myFunction);
         
        myPromisifyFunction()
            .then(function() { ... })
    
bluebird

petkaantonov/bluebird

Promises

        function getAirlinesCb(cb) {
            setTimeout(function () {
                cb(null, 'Airline #123');
            }, 0);
        }
    

        var getAirlines = Promise.promisify(getAirlinesCb);
    

Promises

        function getFlights() {
            return getAirlines()
                .then(getCosts)
                .then(sortAirlines)
                .catch(onReject);
        }
    

Promises

        function getFlights() {
            return getAirlines()
                .then(getCosts)
                .then(sortAirlines)
                .catch(onReject);
        }
    

Promises. getCosts

        function getCosts(airlines) {
            return Promise.props({
                airlines: airlines,
                flightsCost: Promise.all(airlines.map(getCost))
            });
        }
    

Promise

        function getFlights() {
            return getAirlines()
                .then(function (airlines) {
                    return Promise.props({
                        airlines: airlines,
                        flightsCost: Promise.all(airlines.map(getCost))
                    });
                })
                .then(sortAirlines);
        }
         
        getFlights
            .then(function (data) { console.log(data); })
            .catch(function (err) { console.error(err); });
    
Criteria Callback Promises
overheads no yes
level of nesting 5 2
visual noise yes no
separation of data and error no yes
code duplication yes no
missed error yes no
linear code no yes

Perfect code

        function getFlights() {
            var airlines = Get list of airlines()
            var flightsCost = airlines.map(Get flight cost);
            return Sort airlines(airlines, flightsCost);
        };
    

Perfect code

        function getFlights() {
            var airlines = getAirlines();
            var flightsCost = airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        }
    

Perfect code. Sync

        function getFlights() {
            var airlines = getAirlinesSync();
            var flightsCost = airlines.map(getCostSync);
            return sortAirlines(airlines, flightsCost);
        }
    

Perfect code. Generator

        function *getFlights() {
            var airlines = yield getAirlines();
            var flightsCost = yield airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        }
    

Perfect code. Generator

        co(function *getFlights() {
            var airlines = yield getAirlines();
            var flightsCost = yield airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        }).then(function (data) { console.log(data); });
    

Generators

Generator

A generator can pause itself in mid-execution, and can be resumed either right away or at a later time.

ES6

Generator

A generator can pause itself in mid-execution, and can be resumed either right away or at a later time.

ES6 ES2015

Generator. Declaration

        function *myGenerator(name) {
            console.log('Hello, ' + name);
        }
    

Generator. Run

        function *myGenerator(name) {
            console.log('Hello, ' + name);
        }
         
        myGenerator('Frontend Union Conf');
    

Console:

            >
        

Generator. Iterator

An iterator is a structured pattern for pulling information from a source in one-at-a-time fashion.
        it.next()
    

Console:

            { value: {...}, done: false}
            >
        

Generator. Run

        function *myGenerator(name) {
            console.log('Hello, ' + name);
        }
         
        var it = myGenerator('Frontend Union Conf');
        it.next();
    

Console:

            Hello, Frontend Union Conf
            >
        

Generator. Fibonacci

        var it = fibonacci();
         
    

Generator. Fibonacci

        var it = fibonacci();
         
        console.log(it.next().value);
    

Console:

            1
            >
        

Generator. Fibonacci

        var it = fibonacci();
         
        console.log(it.next().value);
        console.log(it.next().value);
    

Console:

            1
            1
            >
        

Generator. Fibonacci

        var it = fibonacci();
         
        console.log(it.next().value);
        console.log(it.next().value);
        console.log(it.next().value);
    

Console:

            1
            1
            2
            >
        

Generator. Fibonacci

        var it = fibonacci();
         
        console.log(it.next().value);
        console.log(it.next().value);
        console.log(it.next().value);
        console.log(it.next().value);
    

Console:

            1
            1
            2
            3
            >
        

Generator. Fibonacci

        var it = fibonacci();
         
        console.log(it.next().value);
        console.log(it.next().value);
        console.log(it.next().value);
        console.log(it.next().value);
        console.log(it.next().value);
    

Console:

            1
            1
            2
            3
            5
            >
        

Generator. Fibonacci

        function *fibonacci() {
            var fn1 = 1;
            var fn2 = 1;
            while (true) {
                var current = fn2;
                fn2 = fn1;
                fn1 = fn1 + current;
                yield current;
            }
        }
    

Generator. yield

        function *fibonacci() {
            var fn1 = 1;
            var fn2 = 1;
            while (true) {
                var current = fn2;
                fn2 = fn1;
                fn1 = fn1 + current;
                yield current;
            }
        }
    

Generator. yield

        
        function *sum() {
            var a = yield;
            var b = yield;
            console.log(a + b);
        }
                
        var it = sum();
    

Console:

            > 
             
        

Generator. yield

        
        function *sum() {
            var a = yield;
            var b = yield;
            console.log(a + b);
        }
                
        var it = sum();
        it.next();
    

Console:

            > 
             
        

Generator. yield

        
        function *sum() {
            var a = 3;
            var b = yield;
            console.log(a + b);
        }
                
        var it = sum();
        it.next();
        it.next(3);
    

Console:

            > 
             
        

Generator. yield

        
        function *sum() {
            var a = 3;
            var b = 7;
            console.log(a + b);
        }
                
        var it = sum();
        it.next();
        it.next(3);
        it.next(7);
    

Console:

            10
            > 
        

Generator. throw

        function *foo() {
            try {
                yield;
            } catch (e) {
                console.error(e.message);
            }
        } 
         
        var it = foo();
        it.next()
        it.throw(new Error('My error!'))
    

Console:

            My error!
            > 
        

Generator

        function *getFlights() {
            var airlines = yield getAirlines();
            var flightsCost = yield airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        }
    

Generator based control flow

Generator based control flow

Generator. co

Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.

tj/co

Generator. co

        var myPromise = co(function *() {
            var value = yield Promise.resolve('Hello, #func!');
            return value;
        });
    

Generator. co

        var myPromise = co(function *() {
            var value = yield Promise.resolve('Hello, #func!');
            return value;
        });
         
        myPromise.then(function (value) {
            console.log(value);
        });
    

Console:

            Hello, #func!
            >
        

Generator

        var getFlights = co(function *() {
            var airlines = yield getAirlines();
            var flightsCost = yield airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        });
    

Generator

        var getFlights = co(function *() {
            var airlines = yield getAirlines();
            var flightsCost = yield airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        });
         
        getFlights
            .then(function (data) { console.log(data); })
            .catch(function (err) { console.error(err); });
    

Generators framework

Promise

        function getFlights() {
            return getAirlines()
                .then(function (airlines) {
                    return Promise.props({
                        airlines: airlines,
                        flightsCost: Promise.all(airlines.map(getCost))
                    });
                })
                .then(sortAirlines);
        }
         
        getFlights
            .then(function (data) { console.log(data); })
            .catch(function (err) { console.error(err); });
    

Generator

        var getFlights = co(function *() {
            var airlines = yield getAirlines();
            var flightsCost = yield airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        });
         
        getFlights
            .then(function (data) { console.log(data); })
            .catch(function (err) { console.error(err); });
    
Criteria Callback Promises Generator
overheads no yes yes
level of nesting 5 2 1
visual noise yes no no
separation of data and error no yes yes
code duplication yes no no
missed error yes no no
linear code no yes yes

Generator. Support

Generator. Babel

babeljs.io
babel

async/await

Perfect code. Async await

        async function getFlights() {
            var airlines = await getAirlines();
            var flightsCost = await airlines.map(getCost);
            return sortAirlines(airlines, flightsCost);
        }
    

Async await. Advantages

Async await. Traceur

traceur

Performance

* Measured in iojs-3.0 on mac air

Contacts

Zhigalov Sergey

Frontend developer

zhigalov@yandex-team.ru

https://clck.ru/9Ztyr

QR