Асинхронность

Жигалов Сергей

ВНИМАНИЕ

Все примеры кода, использованные в данной презентации, вымышлены. Любые совпадения с реальными кодстайлами, живыми или мертвыми, абсолютно случайны. Не повторяйте этого дома!

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

function grist() {
    console.log('Перемолоть кофейные зерна в кофемолке');
}

function addWater() {
    console.log('Добавить немного воды');
}

function toStove() {
    console.log('Поставить на плиту');
}

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

function grist() {
    console.log('Перемолоть кофейные зерна в кофемолке');
}
function addWater() {
    console.log('Добавить немного воды');
}
function toStove() {
    console.log('Поставить на плиту');
}

grist();
addWater();
toStove();
Console:
> Перемолоть кофейные зерна в кофемолке
> Добавить немного воды
> Поставить на плиту

Call stack

function foo() {
    bar();
}
function bar() {
    baz();
}
function baz() {
    console.log('Hello, world!');
}

foo();
Демо 1

Call stack. Размер стека

function callMyself() {
    callMyself();
}
callMyself();
Uncaught RangeError: Maximum call stack size exceeded
    at callMyself (:2:20)
    at callMyself (:3:5)
    at callMyself (:3:5)
    ...

Call stack. Ошибки

function foo() {
    bar();
}
function bar() {
    baz();
}
function baz() {
    throw Error('My error');
}

foo();

Call stack. Ошибки

Error: My error
    at baz (index.js:9:11)
    at bar (index.js:6:5)
    at foo (index.js:3:5)
    at Object. (index.js:12:1)

Call stack

  • При вызове функции - добавляем в стек
  • При завершении функции - убираем из стека

Отложенное выполнение

function turnOn() {
    console.log('Поставить турку на медленный огонь');
}

function turnOff() {
    console.log('Выключить плиту');
}

setTimeout(function () {
    turnOff();
}, 5 * 60 * 1000); // 5 минут
turnOn();

Отложенное выполнение

function turnOn() {
    console.log('Поставить турку на медленный огонь');
}
function turnOff() {
    console.log('Выключить плиту');
}

setTimeout(function () {
    turnOff();
}, 5 * 60 * 1000); // 5 минут
turnOn();
Console:
> Включить плиту на медленный огонь
> Выключить плиту

Таймеры

setTimeout(function () {
    console.log('Добавить корицу');
}, 30 * 1000); // 30 секунд

setInterval(function () {
    console.log('Тщательно перемешать');
}, 30 * 1000); // 30 секунд

Таймеры

setTimeout(function () {
    console.log('Добавить корицу');
}, 30 * 1000); // 30 секунд

setInterval(function () {
    console.log('Тщательно перемешать');
}, 30 * 1000); // 30 секунд
Console:
> Добавить корицу
> Тщательно перемешать
> Тщательно перемешать
> Тщательно перемешать
> Тщательно перемешать

Call Stack

function foo() {
    bar();
}

function bar() {
    baz();
}

function baz() {
    console.log('Hello, world!');
}
setTimeout(foo, 5000);
Демо 2

Event loop

  • Call Stack
  • APIs
  • Event queue

Медленные операции

console.time('Файл');
var data = fs.readFileSync('gang.json');
console.timeEnd('Файл');

console.time('Вычисления');
var moment = robbery.getAppropriateMoment();
var message = moment.format('%HH:%MM!');
console.timeEnd('Вычисления');
Console:
> Файл: 0.581ms
> Вычисления: 2.759ms

Медленные операции

console.time('Файл');
var data = fs.readFileSync('gang.xml'); // 10 mb
console.timeEnd('Файл');

console.time('Вычисления');
var moment = robbery.getAppropriateMoment();
var message = moment.format('%HH:%MM!');
console.timeEnd('Вычисления');
Console:
> Файл: 5.666ms
> Вычисления: 2.735ms

Медленные операции

console.time('Файл');
var data = getFileFromNetwork('https://.../gang.json');
console.timeEnd('Файл');

console.time('Вычисления');
var moment = robbery.getAppropriateMoment();
var message = moment.format('%HH:%MM!');
console.timeEnd('Вычисления');
Console:
> Файл: 167.050ms
> Вычисления: 2.702ms

Операции ввода-вывода (IO) занимают значительно больше времени, чем вычислительные операции.

 

Поток не блокирован

console.log('Начинаем');

setInterval(function () {
    console.log('setInterval сработал');
}, 1000);



Console:
> Начинаем
> setInterval сработал
> setInterval сработал
> setInterval сработал
> setInterval сработал
> setInterval сработал

Поток заблокирован

console.log('Начинаем');

setInterval(function () {
    console.log('setInterval сработал');
}, 1000);

fs.readFileSync('bigFile.txt');
console.log('Файл прочитан');
Console:
> Начинаем
> ...
> ...
> Файл прочитан
> setInterval сработал
> setInterval сработал

Асинхронные функции

console.log('Начинаем');

setInterval(function () {
    console.log('setInterval сработал');
}, 1000);

fs.readFile('bigFile.txt', function () {
    console.log('Файл прочитан');
});
Console:
> Начинаем
> setInterval сработал
> setInterval сработал
> Файл прочитан
> setInterval сработал
> setInterval сработал
Демо 3

Event loop

  • Call Stack
  • APIs
  • Event queue

Подходы к организации асинхронного кода

  1. callback

callback


fs.readFile('bigFile.txt', function (err, data) {
    // содержимое файла лежит в
    // переменной data
});
        

... хм, а что если нужно прочитать из двух файлов и объединить результат?

callback


function readTwoFiles(cb) {
    var otherData;

    fs.readFile('one.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, data + otherData);}
        else { otherData = data; }
    });

    fs.readFile('two.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, otherData + data);}
        else { otherData = data; }
    });
}

callback. Глубокий уровень вложенности

function readTwoFiles(cb) {
    var otherData;
    fs.readFile('one.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, data + otherData);}
        else { otherData = data; }
    });
    fs.readFile('two.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, otherData + data);}
        else { otherData = data; }
    });
}

callback. Разделение ошибок и данных

function readTwoFiles(cb) {
    var otherData;
    fs.readFile('one.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, data + otherData);}
        else { otherData = data; }
    });
    fs.readFile('two.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, otherData + data);}
        else { otherData = data; }
    });
}

callback. Обработка ошибки

function readTwoFiles(cb) {
    var otherData;
    fs.readFile('one.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, data + otherData);}
        else { otherData = data; }
    });
    fs.readFile('two.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, otherData + data);}
        else { otherData = data; }
    });
}

callback Необработанные исключения

function readTwoFiles(cb) {
    var otherData;
    fs.readFile('one.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, data + otherData);}
        else { throw Error('Mu-ha-ha!'); }
    });
    fs.readFile('two.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, otherData + data);}
        else { otherData = data; }
    });
}

callback Лишние переменные

function readTwoFiles(cb) {
    var otherData;
    fs.readFile('one.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, data + otherData);}
        else { otherData = data; }
    });
    fs.readFile('two.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, otherData + data);}
        else { otherData = data; }
    });
}

callback Нет зависимостей

function readTwoFiles(cb) {
    var otherData;
    fs.readFile('one.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, data + otherData);}
        else { otherData = data; }
    });
    fs.readFile('two.txt', function (err, data) {
        if (err) { cb(err); }
        if (otherData) {cb(null, otherData + data);}
        else { otherData = data; }
    });
}

Запускаем и работает :-)

callback

async.js

callback async.js


function readTwoFiles(cb) {
    async.parallel([
        fs.readFile.bind(fs, 'one.txt'),
        fs.readFile.bind(fs, 'two.txt')
    ], function (err, data) {
        cb(err, data[0] + data[1])
    });
}
        

async.js

Подходы к организации асинхронного кода

  1. callback
  2. promises

promises. Состояния

Promise states

promises. Конструктор

new Promise(function (fulfill, reject){ ... });

promises. Конструктор

new Promise(function (fulfill, reject){
    fs.readFile(filename, function (err, data){
        if (err) { reject(err); }
        else { fulfill(res); }
    });
});

promises. Bluebird

var promisify = require('bluebird').promisify;
var one = promisify(fs.readFile.bind(fs, 'one.txt'));

promises. Запуск

myPromise()
    .then(function onSuccess() { ... },
          function onRejected() { ... })
myPromise()
    .then(function onSuccess() { ... })
    .catch(function onRejected() { ... })

разделили объявление и запуск ... и что?

promises. Чейнинг

myPromise()
   .then(function(data1) {...; return data2;})
   .then(function(data2) {return new Promise(...);})
   .then(function(data3) { })

promises. Синхронно

var promisify = require('bluebird').promisify;

var one = promisify(fs.readFile.bind(fs, 'one.txt'));
var two = promisify(fs.readFile.bind(fs, 'two.txt'));
var first;
one()
    .then(function (data) {
        first = data;
        return two();
    })
    .then(function (data) {
        console.log(first + data);
    })
    .catch(function (err) {console.log(err);});

=(

promises. Асинхронно

bluebird

promises. Асинхронно

var promisify = require('bluebird').promisify;

var one = promisify(fs.readFile.bind(fs, 'one.txt'));
var two = promisify(fs.readFile.bind(fs, 'two.txt'));
Promise.all([
        one(),
        two()
    ])
    .then(function (data) {
        console.log(data[0] + data[1]);
    })
    .catch(function (err) {console.log(err);});

=)

Подходы к организации асинхронного кода

  1. callback
  2. promises
  3. async/await

async await

var promisify = require('bluebird').promisify;

var one = promisify(fs.readFile.bind(fs, 'one.txt'));
var two = promisify(fs.readFile.bind(fs, 'two.txt'));
async function readTwoFiles() {
    console.log(
        await one() +
        await two());
}

Подходы к организации асинхронного кода

  1. callback
  2. promises
  3. async/await
  4. generators

generators

var promisify = require('bluebird').promisify;

var one = promisify(fs.readFile.bind(fs, 'one.txt'));
var two = promisify(fs.readFile.bind(fs, 'two.txt'));
function *myGenerator() {
    var data = yield [one(), two()];
    console.log(data[0] + data[1]);
};
var co = require('co');
co(myGenerator);

Домашечка

github.com/urfu-2015/javascript-tasks-9


Deadline
1 декабря
02:59:59.999