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

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

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

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

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

Параллельное выполнение операций

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

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

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

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

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

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

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

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

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

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

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».

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

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

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

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

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».