Как перестать отлаживать
асинхронный код
и начать жить
Андрей Саломатин
FrontendConf, Москва
21.05.2015
Schlecht!Script
Schlecht!Script
function f1*red(a, b) {…}
f1*red(a, b)
function f2*blue(c) {…}
f2*blue(c)
3
Функции имеют цвет
Schlecht!Script
/* OK! */
function outer*blue() {
inner*blue()
}
4
Синие могут вызывать только
другие синие функции
/* NOT OK! */
function outer*blue() {
inner*red()
}
Schlecht!Script
/* OK! */
function outer*red() {
inner2*red()
}
5
Красные могут вызывать и
красные и синие функции
/* OK! */
function outer*red() {
inner2*blue()
}
Писать и вызывать красные
функции больно!
6
Schlecht!Script
7
Красные функции нужно
называть на немецком!
/* Интерпретатор не поймёт */
function authUser*red() {…}
function getName*red() {…}
Schlecht!Script
8
Красные функции нужно
называть на немецком!
/* RICHTIG! */
function benutzerAutorisierung!*rot() {…}
function nameErhalten!*rot() {…}
Как писать на Schlecht!Script?
9
JavaScript
JavaScript
extends
Schlecht!Script
11
Асинхронные функции
это боль
12
JavaScript
• По-другому работают if/else, for, return
• Нет try/catch
• Ломают абстракцию
13
Асинхронные функции это боль
// выполнять последовательно
for (var i = 0; i < 10; i++) {
if (shouldProcess(i)) {
results.push(process(i));
}
}
14
JavaScript
if/for синхронно
function maybeProcess(i) {
if (i >= 10) { return; }
shouldProcess(function(should) {
if (should) {
process(i, function(result) {
results.push(result);
maybeProcess(i++);
});
}
maybeProcess(i++);
});
}
maybeProcess(0);
15
JavaScript
if/for асинхронно
JavaScript
• Не работают if/then, for и т.д.
• Нет try/catch
• Ломают абстракцию
16
Асинхронные функции это боль
Асинхронность
в JavaScript
17
Андрей Саломатин
Productive Mobile
MoscowJS
RadioJS
18
@filipovskii
Асинхронность
Множество событий
Единичная операция
20
Асинхронность: два сценария
Контроль
22
Исключения
23
Единый интерфейс
24
25
Контроль
Исключения
Единый интерфейс
• Работа с множеством событий

EventEmitter

Stream
• Работа с асинхронными операциями

Continuation Passing Style

Promises

Coroutines
27
ES6
• Работа с множеством событий

Async Generators

• Работа с асинхронными операциями

Async/Await



28
ES7
ES6
Работа с множеством
асинхронных
событий
30
31
Работа с множеством асинхронных событий
EventEmitter
Stream
EventEmitter
32
Объект — источник событий
EventEmitter
33
Примеры
Browser: XMLHttpRequest
Node: http.Server
EventEmitter
emitter.addEventListener(eventName, cb);
emitter.removeEventListener(eventName, cb);
34
API
EventEmitter
35
События XMLHttpRequest
progress (n)
load (1)
abort (1)
error (1)
EventEmitter
36
Реализации
Браузер + Node:

EventEmitter3, Tiny Emitter
Node:

Node EventEmitter
37
Работа с множеством асинхронных событий
EventEmitter
Stream
38
EventEmitter
Stream
Работа с множеством асинхронных событий
Поток данных
Stream
39
Stream
40
Примеры
Node: fs.createReadStream(path)
Node: gulp.src(pattern)
Stream
41
Типы потоков
stylus files css files css prefixed files
gulp.src('*.styl') stylus() autoprefixer() gulp.dest('dist')
Stream
42
источник преобразование преобразование потребитель
gulp.src('*.styl') stylus() autoprefixer() gulp.dest('dist')
Типы потоков
43
Stream
44
Реализации
Изоморфные:

RxJS, Kefir, Bacon.js
Node:

Node Streams
45
Работа с множеством асинхронных событий
EventEmitter
Stream
46
Контроль
Исключения
Единый интерфейс
EventEmitter, Stream
Работа с
асинхронными операциями
47
48
Работа с асинхронными операциями
Continuation Passing Style
Promises
Coroutines
Continuation Passing Style
49
Примеры
Browser:
navigator.geolocation.getCurrentPosition(cb)
Node:
fs.stat(path, cb)
try {
var user = fetchUser(userId);
var following = fetchFollowingUsers(userId);
var tweets = fetchTweets(following);
handleResult(tweets);
} catch (err) {
handleError(err);
}
50
Continuation Passing Style
Получить ленту твитов синхронно Получить
ленту твитов асинхронно
fetchUser(userId, function(err, user) {
if (err) { return handleError(err); }
fetchFollowingUsers(user, function(err, following) {
if (err) { return handleError(err); }
fetchTweets(following, function(err, tweets) {
if (err) { return handleError(err); }
handleResult(tweets);
});
});
});
51
Continuation Passing Style
Получить ленту твитов асинхронно
fetchUser(userId, function(err, user) {
if (err) { return handleError(err); }
fetchFollowingUsers(user, function(err, following) {
if (err) { return handleError(err); }
fetchTweets(following, function(err, tweets) {
if (err) { return handleError(err); }
52
Continuation Passing Style
Получить ленту твитов асинхронно
holenBenutzer!*rot(userId, function(err, user) {
if (err) { return handleError*blue(err); }
holenFolgendenBenutzer!*rot(user, function(err, following) {
if (err) { return handleError*blue(err); }
holenTweets!*rot(following, function(err, tweets) {
if (err) { return handleError(err); }
53
Continuation Passing Style
Holen Sie sich die Band tweets asynchron!
54
Schlecht!Script
55
Работа с асинхронными операциями
Continuation Passing Style
Promises
Coroutines
56
Continuation Passing Style
Promises
Coroutines
Работа с асинхронными операциями
Объект — асинхронная
операция
Promises
57
Promise.prototype.then(successCb, errorCb);
58
API
Promises
fetchUser(userId)
.then(fetchFollowingUsers)
.then(fetchTweets)
.then(handleResult, handleError);
59
Promises
API
Promises
vs
Continuation Passing Style
60
61
Реализации
jQuery.Deffered
Bluebird
RSVP
Q
Promises
62
Continuation Passing Style
Promises
Coroutines
Работа с асинхронными операциями
63
Continuation Passing Style
Promises
Coroutines
Работа с асинхронными операциями
Coroutines
64
Coroutines
function getUserName(userId) {
var user = getUser(userId);
return user.name;
}
65
Остановите землю!
Функция, которую можно
приостановить и возобновить
позже
66
Coroutines
function * getUserName (userId) {
var user = yield getUser(userId);
return user.name;
};
67
Генераторы: шаг 1 из 3
Coroutines
function * getUserName (userId) {
var user = yield getUser(userId);
return user.name;
};
68
Coroutines
Генераторы: шаг 1 из 3
getUserName = co.wrap(function * (userId) {
var user = yield getUser(userId);
return user.name;
});
69
Coroutines
Генераторы: шаг 2 из 3
getUserName = co.wrap(function * (userId) {
var user = yield getUser(userId);
return user.name;
});
70
Coroutines
Генераторы: шаг 2 из 3
userNamePromise = getUserName(userId);
71
Coroutines
Генераторы: шаг 3 из 3
Использовать генераторы для
асинхронного кода —
это хак
72
Использовать генераторы для
асинхронного кода —
это хак
73
(но я вас не выдам)
74
Реализации
Браузер + Node (используют транспайлер):

co (generators), task.js (generators)
Node (использует транспайлер):

Fibers
Coroutines
75
Continuation Passing Style
Promises
Coroutines
Работа с асинхронными операциями
76
Контроль
Исключения
Единый интерфейс
CPS, Promises, Coroutines
Работа с множеством событий:
EventEmitter
Stream
Работа с асинхронными операциями:
CPS
Promise
Coroutine
77
ES6
ES7
Работа с асинхронными операциями:
Async/Await
Работа с множеством событий:
Async Generators
80
ES7
Работа с асинхронными операциями:
Async/Await
Работа с множеством событий:
Async Generators
81
ES7ES7
Async/Await
getUserName = co.wrap(function * (userId) {
var user = yield getUser(userId);
return user.name;
});
82
Генераторы
async function getUserName(userId) {
var user = await getUser(userId);
return user.name;
}
83
Async/Await
Async/Await
async function getUserName(userId) {
var user = await getUser(userId);
return user.name;
}
84
Асинхронная функция
Async/Await
Асинхронные функции
легализованы в ES7
85
Async/Await
Работа с асинхронными операциями:
Async/Await
Работа с множеством событий:
Async Generators
86
ES7
Работа с асинхронными операциями:
Async/Await
Работа с множеством событий:
Async Generators
87
ES7
Подписка на события
для людей
Async Generators
88
async function doDraw() {
for (let ev on observe(win, 'mousemove')) {
draw(ev.clientX, ev.clientY);
}
}
89
События DOM
Async Generators
async function doDraw() {
for (let ev on observe(win, 'mousemove')) {
draw(ev.clientX, ev.clientY);
}
}
90
Async Generators
События DOM
doDrawPromise = doDraw();
91
Async Generators
События DOM
async function *filterWSMessages(ws) {
for (let msg on observe(ws, 'message')) {
if (isValid(msg)) yield msg;
}
}
92
WebSocket сообщения
Async Generators
async function *filterWSMessages(ws) {
for (let msg on observe(ws, 'message')) {
if (isValid(msg)) yield msg;
}
}
93
WebSocket сообщения
Async Generators
??? = filterWSMessages(ws);
??? = observe(win, 'mousemove');
??? = observe(ws, 'message');
94
Что возвращает Async Generator?
Async Generators
messagesObservable = filterWSMessages(ws);
eventsObservable = observe(win, 'mousemove');
eventsObservable = observe(ws, 'message');
95
Async Generators
Что возвращает Async Generator?
Observables
Streams
aka
Работа с асинхронными операциями:
Async/Await
Работа с множеством событий:
Async Generators
97
ES7
ES6 и ES7:
асинхронные операции
98
fetchUser(userId, function(err, user) {
if (err) { return handleError(err); }
fetchFollowingUsers(user, function(err, following) {
if (err) { return handleError(err); }
fetchTweets(following, function(err, tweets) {
if (err) { return handleError(err); }
handleResult(tweets);
});
});
});
99
ES6 и ES7
Получить ленту твитов: CPS
fetchUser(userId)
.then(fetchFollowingUsers)
.then(fetchTweets)
.then(handleResult, handleError);
100
Получить ленту твитов: Promise
ES6 и ES7
try {
var user = await fetchUser(userId);
var following = await fetchFollowingUsers(userId);
var tweets = await fetchTweets(following);
handleResult(tweets);
} catch (err) {
handleError(err);
}
101
Получить ленту твитов: Async/Await
ES6 и ES7
ES6 и ES7:
обработка множества событий
102
var handler = function(ev) {
if (canDraw(ev)) {
draw(ev.clientX, ev.clientY);
}
};
window.addEventListener('mousemove', handler);
window.removeEventListener('mousemove', handler); // позже
103
DOM события: EventEmitter
ES6 и ES7
Kefir.fromEvent(window, 'mousemove')
.filter(canDraw)
.onValue(function(ev) {
draw(ev.clientX, ev.clientY)
})
.end(); // позже
104
DOM события: Stream
ES6 и ES7
for (ev on observe(window, 'mousemove')) {
if (canDraw(ev)) {
draw(ev.clientX, ev.clientY);
}
}
105
DOM события: Async Generators
ES6 и ES7
Как перестать отлаживать
асинхронный
код и начать жить
106
Определите задачу
107
Обдумайте ограничения
108
Используйте лучшие практики
109
Schlecht!Script?
Спасибо!
bit.ly/async-js
Андрей Саломатин
@filipovskii
FrontendConf, Москва
21.05.2015

"Как перестать отлаживать асинхронный код и начать жить", Андрей Саломатин, FrontendConf 2015