Зарегистрируйтесь для доступа к 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 — библиотека для работы в асинхронном стиле

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

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

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

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов веб-приложений
29 сентября 8 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов веб-приложений
29 сентября 8 месяцев

Есть вопрос или хотите участвовать в обсуждении?

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

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»