Promise — это не больно!

Михаил Давыдов

Promise — это не больно!

Михаил Давыдов, JavaScript-разработчик

2013 год, FrontTalks

Асинхронность везде!

Запросы к серверу

Данные пользователя

GARMOSHKA

События интерфейса

Таймеры и анимация

Callback!

Когда один callback — всё здорово!

Последовательные запросы

    login('user:pass@server', function (err, server) {
        if (err) return cb(err);
        server.open('db', function (err, db) {
            if (err) return cb(err);
            db.query(query, function (err, view) {});
        });
    });

Параллельные запросы

    var rows = [], total = view.length;
    function fetch(err, row) {
        if (err) return cb(err);
        if (rows.length === total) cb(null, rows);
    }
    for (var i = 0; i < total; i++) {
        view.get(i, fetch);
    }

Шум

    login('user:pass@server', function (err, server) {
        if (err) return cb(err);
        server.open('db', function (err, db) {
            if (err) return cb(err);
            db.query(query, function (err, view) {});
        });
    });

Шум

Сложно отменить действие

            db.query(query, function (err, view) {});
        

Как отменить или игнорировать запрос в этом случае?

            db.query(query, fn).abort(); // .cancel() ?
            db.query(query, function (err) {
                if (err.message === 'abort') return;
            });
        

Несколько обработчиков

            query(query1, function (err, view) {});
            query(query2, function (err, view) {});
            query(query3, function (err, view) {});
        

3 запроса — 3 попытки логина. Для кэша нужно еще дописать код.

Делегирование результата

            function uberQueryGenerator(data) {
                // Готовим queryFromData
                return query(queryFromData);
            }
        
            function uberQueryGenerator(data, fn) {
                query(queryFromData, fn);
            }
        

У callback-ов нет единого интерфейса

Нет единого интерфейса

Нет единого интерфейса

            $('div').animate({}, function () {});
            // А можно еще вот так
            $('div').animate({}, {
                complete: function () {}
            });
        

Нет единого интерфейса

            $.ajax({
                url: url
                success: function () {}
            });

            $.ajax({
                url: url
            }).done(function () {});
        

Магия

Магия

Step.js

            Step(function login() {
                login('user:pass@server', this);
            }, function openDatabase(err, db) {
                if (err) throw err;
                db.query(query, this);
            });
        

Streamline.js

            var db = login('user:pass@server', _);
            db.query(query, _);
        

Вроде бы ничего!

Streamline.js

            (fstreamline__.create(function(_) {
            var db = (yield fstreamline__
            .invoke(null, login, ['user:pass@server', _], 1));
            (yield fstreamline__.invoke(db, "query", [query, _], 1));
            ;yield;}, 0).call(this, function(err) {
                if (err) throw err;
            }));
        

Promise

Promise

            login('user:pass@server')
            .then(function (server) {
                return server.open('db');
            })
            .then(function (db) {
                return db.query(query);
            })
        

Promise

            .then(function (view) {
                return fetchRows(view);
            }, function handleError(err) {
                console.error(err);
            }) // ...
        

Меньше шума, обработка ошибок в одном месте

Абстракция над обещанными данными

Реализация Promise

            var Promise = function () {
            	this._value = null;
            	this.isFulfilled = false; // ok
            	this.isRejected = false;  // fail
            	this.isResolved = false;  // ok || fail
            	// ...
            };
        

Реализация Promise

            Promise.prototype = {
                // @return {Promise}
                then: function (onFulfilled, onRejected) {},
                fulfill: function (data) {},
                reject: function (error) {}
            };
        

Использование Promise

            // @param {Number} time
            // @return {Promise}
            function timeout(time) {
                var promise = new Promise();
                setTimeout(promise.fulfill, time);
                return promise;
            }
        

Пример timeout

            timeout(1000)
            .then(function () {
                console.log('first');
                return timeout(1000);
            })
            .then(console.log.bind(console, 'second!'));
        

   	

Пример timeout

            timeout(1000)
            .then(randomLog)
            .then(console.log.bind(console, 'done!'));
            function randomLog() {
                var rnd = Math.random();
                console.log(rnd);
                if (rnd < 0.5) return timeout(2000);
            }
        

   	

Promise

Трюки с Promise

Склеивание Promise

            // $.when - агрегатор Promise
            // Результат не раньше, чем через 1с
            $.when($.get('/'), $.get('/?'), timeout(1000))
            .then(function (res) {
                console.log(res[0].length + res[1].length);
            });
        

	

Повтор Promise

            new Attempt(get404, repeat3Times)
                .then(ok, epicFail, progress);
            function get404() {
                return $.get('/404');
            }
            function repeat3Times(err, num) {
                if (num < 4) return 1000;
            }
        

	

Прозрачный кэш с Promise

            var cache;
            function request() {
                return cache ? cache : cache = $.get('/');
            }
            request().then(function (html) {
                console.log(html.length);
            });
        

	

Promise уже рядом!

Generators

Generators

            var co = require('co');
            co(function *() {
                var server = yield login('user:pass@server'),
                    db = yield server.open('db'),
                    view = yield db.query(query);
            });
        

Скоро на экранах ваших IDE

yield vs then?

Generators и Promise

Используйте Promise!

Promise — это не больно!

Михаил Давыдов

JavaScript-разработчик

Почитать

clck.ru/8i9pr