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

Основы вычислительного параллелизма в JavaScript

Однопоточное выполнение

В JavaScript весь программный код выполняется в одном вычислительном потоке (треде).

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

Событийный параллелизм

Параллелизм в JavaScript основан на модели событий:

  1. каждая подпрограмма выполняется до полного завершения (т.е. пока стек вызовов не окажется пустым);
  2. далее среда исполнения ожидает появления следующей подпрограммы в очереди событий;
  3. все подпрограммы из очереди событий выполняются согласно пункту 1 в единственном вычислительном потоке.

О каких событиях идет речь?

События — это, как правило, внешние явления по отношению к Вашей программе, например:

  • в веб-браузере событиями являются действия пользователя (например, щелчок мышью по кнопке);
  • в Node.js все операции ввода-вывода основаны на событиях (например, начало чтения из файла, получены данные, достигнут конец файла).

Таймер

Самый простой пример события — это срабатывание таймера с помощью стандартных функций setTimeout и setInterval.

Эти функции являются частью спецификации языка, поэтому доступны как в браузере, так и в Node.js.

setTimeout

Функция setTimeout выполняет заданную функцию через указанное время (в миллисекундах).

setTimeout(function() {
  console.log('Hi!');
}, 5000);

Попробуйте! Строка Hi! появится в консоли через 5 секунд.

setTimeout

Еще более важно то, что функция setTimeout является асинхронной: она не выполяет код сразу, а лишь регистриует обработчик события срабатывания таймера.

Сразу же после этого продолжается выполнение следующих операторов.

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);

Не продолжайте дальше, пока не поймете этот пример.

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

Вот мы и дошли до определения.

Асинхронные функции регистрируют обработчик события и выходят, как можно скорее освобождая поток выполнения.

Синхронные функции

Очевидно, что противоположностью асинхронных функций являются синхронные — это функции, производящие все вычисления в активном потоке исполнения.

Длительные синхронные вычисления (например, нахождение большого числа Фиббоначи) приводят к проблемам с производительностью приложения, т.к. не позволяют ему реагировать на остальные события.

Синхронные функции — это не «плохо».

Не следует избегать всех синхронных функций — это попросту невозможно.

Следует лишь избегать синхронного ввода-вывода, когда программа ожидает данные, но не реагирует на события.

Синхронный vs. Асинхронный

Асинхронное программирование отличается от императивного.

Синхронный (императивный) стиль

Большинство программистов привыкли думать в императивном стиле:

// Алгоритм приготовления чая
var cup = getCup();
var teabag = getTeabag();
cup.put(teabag);
cup.put(getHotWater());

Acинхронный (событийный) стиль

В асинхронном программировании все по-другому:

// Алгоритм приготовления чая
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);
  });

Задания для самоподготовки

  • Найдите заинтересованного собеседника и объясните ему модель параллелизма в JavaScript.
  • Объясните собеседнику пример с циклом for и setTimeout.
В начало1 из 23ВыйтиЗавершить просмотр