Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Параллельное выполнение операций JS: Асинхронное программирование

Попытаемся выполнить несколько асинхронных вызовов одновременно и затем воспользоваться их результатом. Попробуем переписать нашу задачу по объединению файлов. Вот её текст:

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

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

fs.readFile('./first', 'utf-8', (_error1, data1) => {
  // ?
});

fs.readFile('./second', 'utf-8', (_error2, data2) => {
  // ?
});

Так как наш код асинхронный, результат работы каждой функции можно получить лишь внутри колбеков. Причём, порядок запуска колбеков мы не можем знать — всё зависит от того, какой файл прочитается быстрее. Для отслеживания состояния выполнения этих операций придётся ввести глобальное состояние (относительно этих операций), через которое мы будем отслеживать завершённость и в котором сохраним данные. И только когда все операции завершились — запишем новый файл. Кроме того, нам нужно чётко разделять данные первого и второго файлов, так как запись в новый файл (в отличие от чтения) должна происходить в определённом порядке.

const state = {
  count: 0,
  results: [],
};

fs.readFile('./first', 'utf-8', (_error1, data1) => {
  state.count += 1;
  state.results[0] = data1;
});

fs.readFile('./second', 'utf-8', (_error2, data2) => {
  state.count += 1;
  state.results[1] = data2;
});

Когда обе операции завершатся, состояние заполнится данными, а значение count станет 2. Именно на это условие мы и завяжем наш код:

import fs from 'fs';

const state = {
  count: 0,
  results: [],
};

const tryWriteNewFile = () => {
  if (state.count !== 2) {
    return; // guard expression
  }

  fs.writeFile('./new-file', state.results.join(''), (error) => {
    if (error) {
      return;
    }
    console.log('finished!');
  });
};

console.log('first reading was started');
fs.readFile('./first', 'utf-8', (error1, data1) => {
  console.log('first callback');
  if (error1) {
    return;
  }
  state.count += 1;
  state.results[0] = data1;
  tryWriteNewFile();
});

console.log('second reading was started');
fs.readFile('./second', 'utf-8', (error2, data2) => {
  console.log('second callback');
  if (error2) {
    return;
  }
  state.count += 1;
  state.results[1] = data2;
  tryWriteNewFile();
});

// Один запуск
// node index.js
// first reading was started
// second reading was started
// second callback
// first callback
// finished!

// Другой запуск
// node index.js
// first reading was started
// second reading was started
// first callback
// second callback
// finished!

Теперь файлы читаются параллельно и мы, наконец-то, увидели на практике преимущество одновременного выполнения асинхронных операций. Скорость выполнения этой программы значительно выше синхронного варианта! Причём, чем больше размер файлов, тем больше разница. Однако, стоит заметить, что, хотя чтение файлов происходит параллельно, работа самого js, обрабатывающего результат — строго последовательна. Колбеки начинают запускаться только после того, как опустеет текущий стек вызовов, и в том порядке, в котором завершились асинхронные операции (параллельный запуск не означает, что операции заканчиваются и начинаются одновременно).

Каждый раз писать подобный код очень утомительно, поэтому лучше воспользоваться библиотекой под названием async, которая предоставляет набор готовых абстракций для работы в асинхронном стиле. Она содержит десятки функций для большого числа задач, связанных с упорядочиванием асинхронных операций. Ниже пример решения нашей задачи с использованием этой библиотеки:

import { map } from 'async';
import fs from 'fs';

map(['./first', './second'], fs.readFile, (err1, results) => {
  if (err1) {
    return;
  }
  fs.writeFile('./new-file', results.join(''), (err2) => {
    if (err2) {
      return;
    }
    console.log('finished!');
  });
});

Согласитесь, что это значительно лучше ;) Но, как увидите позже, можно пойти еще дальше.


Дополнительные материалы

  1. async — библиотека для работы в асинхронном стиле

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
Иконка программы Фронтенд-разработчик
Профессия
с нуля
Разработка фронтенд-компонентов для веб-приложений
23 марта 10 месяцев
Иконка программы Онлайн-буткемп. Фронтенд-разработчик
Профессия
Новый с нуля
Интенсивное обучение профессии в режиме полного дня
20 апреля 4 месяца
Иконка программы Node.js-разработчик
Профессия
с нуля
Разработка бэкенд-компонентов для веб-приложений
23 марта 10 месяцев
Иконка программы Fullstack-разработчик
Профессия
с нуля
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
23 марта 16 месяцев

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»