Обещания Javascript помогают разработчикам писать чистый код. Но как выглядит плохой JS-код…

Пока вы создаете серверные службы с помощью Node JS, важно знать, как Javascript изменил свой стиль кодирования, чтобы помочь разработчикам писать более качественный и чистый код. Вы не хотите, чтобы вас поймали за написанием кода в старом стиле среди ваших коллег 😃. Итак, давайте начнем наше путешествие со стилей кодирования Javascript и узнаем, почему были введены Promise.

Во-первых, код, который удобен для разработчиков, но плох для JS.

Насколько простым будет код, если мы напишем его, как показано ниже:

(Воображаемый разговор между JS и разработчиком😃)
Разработчик:
Это настолько интуитивно понятно, легко читается и понимается. Все инструкции идут одна за другой.
JS: Кричит вслух. ЭТО ПЛОХОЙ КОД.
Разработчик: Почему так?
JS: Я однопоточный и могу выполнять только одну задачу за раз. Каждый оператор блокирует поток до его завершения. Все они являются синхронными операторами.
Разработчик: Но первые три оператора зависят друг от друга. Они не могут выполняться, пока функция над ними не завершится.
JS: Согласен. Но почему someOtherFunctionNotDependentOnAbove(); нужно ждать завершения приведенного выше кода?
Разработчик: Да, но что мы можем сделать?
JS: Воспользуйтесь моим секретный ингредиент, обратные вызовы !!!, измените свой код на асинхронные операторы и разрешите обратным вызовам работать за кулисами.

Второй в очереди, стиль кода с обратным вызовом, хороший для JS, но сложный для чтения и написания разработчиками.

Следуя приведенному выше примеру, давайте напишем реальный код, полностью блокирующий или синхронный.

В приведенном выше коде есть все операторы блокировки, и, как мы видим, чтение 1000 файлов займет значительное время. Javascript никогда не поощряет этот код, а использует обратные вызовы, как показано ниже.

Мы сразу видим преимущества написания асинхронных вызовов. Каждый вызов функции является асинхронным, и зависимости между функциями обрабатываются обратными вызовами. Но у каждой медали есть две стороны. Обратные вызовы обеспечивают многозадачность в коде, но начинают создавать проблемы ад обратных вызовов или Пирамида судьбы. Если приведенный выше код выглядит не так уж плохо, посмотрите реальный пример на callbackhell.com.

Так что же хорошо и для разработчиков, и для JS? Входите, Обещания.

Обратные вызовы являются нормой для JS, но они создают проблему «ада обратных вызовов». Итак, JS решает эту проблему, используя класс Promise. Функция, возвращающая Promise, гарантирует, что вызываемая функция является асинхронной и не блокирует поток выполнения. Для начала давайте посмотрим, как определить функцию, которая возвращает Promise.

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

// First function which returns an Array of filenames as Promise
function readDir() {
return new Promise(function(resolve,reject) {
fs.readdir('/pathToDirWith1000Files',function(err,fileList) {
if(err)
reject(err)
else
resolve(fileList)
})
})
}
// Second function that returns array of Promises from below func
function readFiles(fileList) {
var filePromises = fileList.map(function(fileName) {
return readFile(fileName)
})
return Promise.all(filePromises)
}
// Third function that returns Buffer as a Promise
function readFile(fileName) {
return new Promise(function(resolve,reject) {
fs.readFile(fileName,function(err,buffer) {
if(err)
reject(err)
else
resolve(buffer)
})
})
}
// Main execution
readDir()
.then(fileList=>readFiles(fileList))
.then(buffer=>console.log(buffer))
.catch(err=>console.log(err))

Разве основная казнь не выглядит милой и простой? Но осталось немного доработать.

  1. Существует много шаблонного кода для создания функции, возвращающей Promise. Это можно исправить с помощью async функций. (Осторожно, в этом примере модуль fs нуждается в promisify util для работы async)
  2. Используя async и await, мы можем вернуться к исходному стилю написания кода без потери преимуществ JS.

Окончательный стиль, код, который выглядит синхронно, но работает асинхронно

// Main execution
var callAsyncCode = async function() {
var fileList = await readDir()
var filePromises = await readFiles(fileList)
filePromises.map(buffer=>console.log(buffer))
}()

Таким образом, async и await вместе образуют синтаксический сахар для Promise, с помощью которого разработчики могут писать синхронный код, но при этом достигать многозадачности, используя возможности обратных вызовов.

Резюме

Я надеюсь, что эта статья освежила воспоминания о кодировании в старом стиле для всех опытных JS-разработчиков, а также, надеюсь, предоставила некоторую предысторию для всех недавних разработчиков, надеющихся на JS….