Control flow in Node.js — это своего рода библиотека, для которой почти все разработчики создали и публикуют свои собственные библиотеки. Обычно они стремятся сократить количество спагетти-кодов, состоящих из глубоких обратных вызовов. Я не исключение из этого правила. После полутора лет интенсивного использования я чувствую, что пришло время представить каждому из них мою собственную библиотеку потоков управления.
Ну, если быть точным, это не библиотека потоков управления в традиционном смысле. Такого механизма для объединения функций в цепочки и управления ими не существует. Это произошло из-за моей острой необходимости просматривать массивы и вызывать асинхронный код для каждого из их элементов. Подумайте о Array.prototype.forEach на стероидах.
Допустим, нам нужно создать 3 каталога. Эта операция может выполняться параллельно и состоять из 3-х подопераций: проверить, существует ли каталог, создать каталог и предоставить разрешения.
Вот код:
каждый([ ‘/data/1/my_dir’ ‘/data/2/my_dir’ ‘/data/3/my_dir’ ]).на ‘item’, (dir, next) -> fs.stat dir, (err, stat) -> вернуть следующий(), если stat fs.mkdir dir, (err) -> следующая ошибка.при «ошибке», (err) -> сообщение об ошибке консоли.при «завершении», -> консоль.зарегистрируйте «Успех»
Как вы можете видеть, каждый из них заимствует свой API у источника событий и потоковых модулей в Node.js.
Кажется неудобным начинать таким образом, но поскольку каждая из них частично является Node.js библиотека потоков управления, мне кажется важным объяснить, почему она не отвечает всем потребностям и почему я не использую какую-либо существующую библиотеку в дополнение к каждой из них.
Асинхронное программирование — это здорово, но в Node.js и Javasript оно приводит к неэстетичному коду, в котором обратные вызовы вызывают больше обратных вызовов, часто называемых спагетти-кодом.Давайте вернемся к нашему примеру, приведенному выше. Один из способов ограничить глубину кода — это изолировать процесс создания каталога в рамках одной функции:
create = (dir, обратный вызов) -> fs.stat dir, (ошибка, stat) -> return next(), если stat fs.mkdir dir, (ошибка) -> следующая ошибка
Однако библиотеки потоков управления полезны не только для уменьшения глубины кода. Они также решают сложные задачи. Давайте предположим, что нам нужно создать файл, независимо от того, существует ли этот каталог или нет:
create = (файл, содержимое, обратный вызов) -> dir = путь.имя_файла fs.stat dir, (ошибка, статистика) -> if stat fs.WriteFile файл, содержимое, (ошибка) -> ошибка обратного вызова fs.mkdir dir, (ошибка) -> fs.WriteFile файл, содержимое, (ошибка) -> ошибка обратного вызова
Здесь код для записи файла не просто избыточен и уродлив, он может стать действительно сложным, когда сложность вашего кода увеличивается. После использования различных библиотек я, наконец, пришел к выводу, что наилучшим подходом к решению этой проблемы является разбивка кода на небольшие функции. Вот как это делается:
create = (файл, содержимое, обратный вызов) -> dir = path.dirname checkDir = -> fs.stat dir, (ошибка, статистика) -> если только stat, затем MakeDir() else WriteFile() MakeDir = -> fs.mkdir dir, (ошибка) -> вернуть ошибку обратного вызова, если ошибка WriteFile()] WriteFile = fs.Файл записи, содержимое, (ошибка) -> ошибка обратного вызова checkDir().on(‘элемент’, создать).on ‘ошибка’, (ошибка) -> сообщение об ошибке консоли.error.on «конец», -> консоль.в результате получается нативное решение на JavaScript, которое легко читается и эффективно запускается. Исходный код mecano — хороший ресурс, иллюстрирующий эту закономерность.
Есть еще одно полезное применение библиотек потока управления. Они позволяют выполнять итерации асинхронно. Не существует красивого и абсолютно простого способа добиться этого на чистом JavaScript. Все становится чрезмерно сложным, когда вам нужно иметь дело с корректной обработкой ошибок или с ограниченным количеством параллельных задач.
Вот как я придумал каждую из них. В то время я устанавливал и запускал кластер Hadoop, и мои задачи должны были быть распределены по всему кластеру. Такие вещи, как запуск процессов, выполнение распределенных команд или сбор статистики, выполнялись (и до сих пор выполняются) каждым пользователем, и, конечно же, Node.js.
Со временем библиотека стала более гибкой и протестированной. API — это API-интерфейс для создания событий, классический для библиотеки Node.js. Он также частично заимствован из Stream API.
Чем больше я использовал каждый из них, тем больше понимал, что мои проблемы не связаны с асинхронным вызовом функций. Каждый раз, когда у меня возникало искушение использовать библиотеку потоков управления, я сталкивался с необходимостью обхода массивов. Асинхронная итерация массивов — сложный процесс, и каждый из них решается элегантно. Я приглашаю вас попробовать каждый из них и сделать его еще лучше.
Опять же, исходный код mecano (теперь Nikita) — отличный источник вдохновения, если вы хотите увидеть каждый из них в действии.
Спасибо за чтение. Пожалуйста, ознакомьтесь с исходным кодом на GitHub.