Асинхронные функции
Основы вычислительного параллелизма в JavaScript
Основы вычислительного параллелизма в JavaScript
В JavaScript весь программный код выполняется в одном вычислительном потоке (треде).
В других языках программирования возможна ситуация, при которой между двумя соседними операторами может выполниться код из другого потока. С многопоточностью связано множество трудноуловимых проблем, включая взаимные блокировки или гонки за ресурсами.
Параллелизм в JavaScript основан на модели событий:
События — это, как правило, внешние явления по отношению к Вашей программе, например:
Самый простой пример события — это срабатывание таймера с помощью стандартных функций setTimeout
и setInterval
.
Эти функции являются частью спецификации языка, поэтому доступны как в браузере, так и в Node.js.
Функция setTimeout
выполняет заданную функцию через указанное время (в миллисекундах).
setTimeout(function() {
console.log('Hi!');
}, 5000);
Попробуйте! Строка Hi!
появится в консоли через 5 секунд.
Еще более важно то, что функция setTimeout
является асинхронной: она не выполяет код сразу, а лишь регистриует обработчик события срабатывания таймера.
Сразу же после этого продолжается выполнение следующих операторов.
Выполните следующий код в консоли.
setTimeout(function() {
console.log('Hi!');
}, 5000);
console.log('Done.');
Вы увидите, что сначала появилась надпись Done.
, а лишь через 5 секунд надпись Hi!
.
Почему так?
Чтобы понять предыдущий пример, давайте разберем его с точки зрения событийной модели.
1. каждая подпрограмма выполняется до полного завершения (т.е. пока стек вызовов не окажется пустым);
Таким образом, в начале мы имеем программу из двух выражений:
setTimeout(function() { ... }, 5000);
console.log('Done.');
setTimeout
добавляет обработчик события и выходит, сразу же после нее выполняется console.log('Done')
.
2. далее среда исполнения ожидает появления следующей подпрограммы в очереди событий
Через пять секунд происходит событие — срабатывает таймер. Обработчик этого события помещается в очередь на выполнение.
3. все подпрограммы из очереди событий выполняются согласно пункту 1 в единственном вычислительном потоке.
Теперь управление получает анонимная функция-обработчик function() { console.log('Hi!') }
.
Теперь Вам должно быть понятно, почему в следующем примере в консоли сначала появляется Done.
, а затем сразу же Hi!
.
setTimeout(function() {
console.log('Hi!');
}, 0);
console.log('Done.');
Тогда объясните, почему в следующем примере в консоль три раза подряд выводится число 3
.
for (var i = 0; i < 3; i++)
setTimeout(function() {
console.log(i);
}, 0);
Не продолжайте дальше, пока не поймете этот пример.
Вот мы и дошли до определения.
Асинхронные функции регистрируют обработчик события и выходят, как можно скорее освобождая поток выполнения.
Очевидно, что противоположностью асинхронных функций являются синхронные — это функции, производящие все вычисления в активном потоке исполнения.
Длительные синхронные вычисления (например, нахождение большого числа Фиббоначи) приводят к проблемам с производительностью приложения, т.к. не позволяют ему реагировать на остальные события.
Синхронные функции — это не «плохо».
Не следует избегать всех синхронных функций — это попросту невозможно.
Следует лишь избегать синхронного ввода-вывода, когда программа ожидает данные, но не реагирует на события.
Асинхронное программирование отличается от императивного.
Большинство программистов привыкли думать в императивном стиле:
// Алгоритм приготовления чая
var cup = getCup();
var teabag = getTeabag();
cup.put(teabag);
cup.put(getHotWater());
В асинхронном программировании все по-другому:
// Алгоритм приготовления чая
getCup(function(cup) {
getTeabag(function(teabag) {
getHotWater(function(hotWater) {
cup.put(teabag);
cup.put(hotWater);
})
});
});
Для ясности в этом примере мы опустили обработку ошибок.
Такие библиотеки, как async или underscore, позволяют сгладить некоторые неудобства, а также сделать код более эффективным и читаемым:
async.parallel([getCup, getTeabag, getHotWater],
function(err, results) {
var cup = results[0];
var teabag = results[1];
var hotWater = results[2];
cup.put(teabag);
cup.put(hotWater);
});
for
и setTimeout
.