Тяжёлое бремя времени:
типичные ошибки при работе со временем

Виктор Хомяков, Яндекс

Тяжёлое бремя времени:
типичные ошибки при работе со временем

Logo

Виктор Хомяков, Яндекс

О себе

Я работал в трёх продуктовых компаниях, видел много кода в проектах на разных языках и фреймворках

C# Java JavaScript Ruby

Виктор Хомяков

Что я увидел

О чём хочу рассказать

Часть первая.
Время назад

1.1 Время не стоит на месте

Пример: запрос в базу за вчера

const startDate = new Date();
startDate.setDate(startDate.getDate() - 1); // вчера

const endDate = new Date();

queryDatabase(startDate, endDate);
// запрос за дату date >= вчера && date < сегодня
        

Пример: запрос в базу за вчера?

const startDate = new Date();               // 2019-05-22
startDate.setDate(startDate.getDate() - 1); // 2019-05-21

const endDate = new Date();                 // 2019-05-23

queryDatabase(startDate, endDate);
// запрос за дату date >= 2019-05-21 && date < 2019-05-23
        

Проблемы, тимлид?

Мораль

1.2 Измерение интервалов и логирование событий

Логируем транзакции

const start = Date.now();
transaction();
const finish = Date.now();
log(`Транзакция длилась ${finish - start}мс`);
        

Логируем транзакции

const start = Date.now();                      // 1550000000000
transaction();
const finish = Date.now();                     // 1549999999000
log(`Транзакция длилась ${finish - start}мс`); // Транзакция длилась -1000мс
        

Мораль

Бэкэнд

Распределённая система

Clock 7 Clock 2 Clock 9

Вектор времени или счётчики событий

Фронт + бэк = распределённая система

Часть вторая.
Такая асинхронная асинхронность

2.1 AJAX и коллбэки

Пример: suggest

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

ajax(query1, callback);
        

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

ajax(query1, callback);
ajax(query2, callback);
        

Последовательность коллбэков не гарантирована!

Плохой suggest

Варианты выполнения

Sequence 1

Sequence 2

Sequence 3

Часто встречается на UI

Мораль

Если есть источник однотипных запросов:

В RxJS это уже решено

var keyups = Rx.Observable.fromEvent($('#input'), 'keyup')
    .pluck('target', 'value')
    .filter(text => text.length > 2)
    .debounce(500 /* миллисекунд */)
    .distinctUntilChanged();

var suggestions = keyups.flatMapLatest(ajax); // игнорируем старые ответы
        

2.2 О жизни React-компонентов

Что не так в этом коде?

class MapControls extends React.PureComponent {
    // ...
    onLocationError() {
        // Показываем плашку ошибки
        this.setState({ locationFailed: true });

        // Убираем плашку ошибки через 3 секунды
        setTimeout(() => {
            this.setState({ locationFailed: false });
        }, 3000);
    }
}
        

Что не так в этом коде?

class MapControls extends React.PureComponent {
    // ...
    onLocationError() {
        // Показываем плашку ошибки
        this.setState({ locationFailed: true });

        // Убираем плашку ошибки через 3 секунды
        setTimeout(() => {
            this.setState({ locationFailed: false });
        }, 3000);
    }
}
        

Не забываем про unmount

class MapControls extends React.PureComponent {
    onLocationError() {
        // Показываем плашку ошибки
        this.setState({ locationFailed: true });
        // Убираем плашку ошибки через 3 секунды
        this.timer = setTimeout(() => {
            this.setState({ locationFailed: false });
        }, 3000);
    }
    componentWillUnmount() {
        window.clearTimeout(this.timer);
    }
}
        

Методы могут вызваться с задержкой:

Мораль

Причина проблем — несовпадение времени жизни объектов

Часть третья.
Нежданная синхронность

3.1 Promise

Что в этом коде можно ускорить?

prepare()
    .then(() => query1())
    .then(() => query2())
    .then(() => {
        // обработка ответов
    });
        

Что в этом коде можно ускорить?

prepare()
    .then(() => query1())
    .then(() => query2())
    .then(() => {
        // обработка ответов
    });
        

Независимые then можно распараллелить

Управляем асинхронным кодом

Promise API

async

bluebird bluebird

Распараллелим независимые запросы

Promise.all([query1(), query2()])
    .then(() => {
        // обработка ответов
    });
        

3.2 Await

Используя await, главное — не перестараться

const url = await browser.getUrl();
const href = await browser.getAttribute(selector, 'href');
        

Используя await, главное — не перестараться

const url = await browser.getUrl();                        // ждём выполнения
const href = await browser.getAttribute(selector, 'href');
        

Используя await, главное — не перестараться

const url = await browser.getUrl();                        // ждём выполнения
const href = await browser.getAttribute(selector, 'href'); // ждём выполнения
        
browser.getUrl()
    .then(result => url = result)
    .then(() => browser.getAttribute(selector, 'href'))
    .then(result => href = result);
        

Распараллелим независимые запросы

const [url, href] = await Promise.all(
    [browser.getUrl(), browser.getAttribute(selector, 'href')]
);
        

Мораль

Материалы

Заключение

Спасибо! Вопросы?

Виктор Хомяков

разработчик интерфейсов