One place for hosting & domains

      Написание

      Написание асинхронного кода в Node.js


      Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Во многих программах JavaScript код выполняется так, как написал его разработчик — строка за строкой. Это называется синхронным выполнением, поскольку строки выполняются друг за другом в порядке, в котором они были написаны. Однако не все инструкции, которые вы передаете компьютеру, должны быть приняты сразу. Например, если вы отправите сетевой запрос, процесс выполнения вашего кода вынужден ждать, пока данные будут возвращены, прежде чем можно будет продолжить работу. В данном случае время будет потрачено зря, если другой код не будет выполняться, пока вы ждете завершения выполнения запроса. Чтобы решить эту проблему, разработчики используют асинхронное программирование, в котором строки кода выполняются в ином порядке, чем в том, в каком они написаны. С помощью асинхронного программирования мы можем выполнять другой код, ожидая завершения длительных действий, например сетевых запросов.

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

      В этом обучающем руководстве вы узнаете, как JavaScript управляет асинхронными задачами с помощью цикла событий, конструкции JavaScript, которая выполняет новую задачу, пока выполняется другая. Затем вы создадите программу, которая использует асинхронное программирование, чтобы запрашивать список фильмов из Studio Ghibli API, и сохраняет данные в файл CSV. Асинхронный код будет написан тремя разными способами: обратные вызовы, обещания и ключевые слова async/await.

      Примечание. На момент написания данной статьи асинхронное программирование больше не использует исключительно обратные вызовы, но изучение этого устаревшего метода позволяет получить более широкое понимание того, почему сообщество JavaScript сейчас использует обещания. Ключевые слова async/await позволяют нам использовать обещания менее многословным образом, поэтому они являются стандартом для асинхронного программирования в JavaScript на момент написания этой статьи.

      Предварительные требования

      Цикл событий

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

      Так как интерпретатор JavaScript выполняет код, все функции, вызываемые JavaScript, добавляются в стек вызовов. Стек вызовов — это стек, структура данных в виде списка, где элементы могут быть добавлены только сверху и удалены только сверху. Стек опирается на принцип «последний пришел, первый ушел» (или LIFO). Если вы добавили два элемента в стек, последний добавленный элемент удаляется в первую очередь.

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

      Когда JavaScript выполняет асинхронную операцию, например запись в файл, она добавляется в таблицу в памяти. Эта таблица хранит операцию, условие ее завершения и функцию, которая будет вызываться после ее завершения. После завершения операции JavaScript добавляет связанную функцию в очередь сообщений. Очередь — это другая структура данных в виде списка, где элементы могут быть добавлены только в конец очереди, а удаляются только из начала очереди. В очереди сообщений, если две или более асинхронные операции готовы к выполнению своих функций, асинхронная операция, которая была выполнена в первую очередь, будет иметь функцию, которая помечена на выполнение также в первую очередь.

      Функции в очереди сообщений ожидают добавления в стек вызовов. Цикл событий — это бесконечный процесс, который проверяет, есть ли в стеке вызовов записи. Если записи отсутствуют, первый элемент в очереди сообщений передается в стек вызовов. JavaScript присваивает приоритет функциям в очереди сообщений согласно вызовам функции, которые обнаруживает в коде. Совокупный эффект стека вызовов, очереди сообщений и цикла событий позволяет обрабатывать код JavaScript при управлении асинхронными действиями.

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

      Асинхронное программирование с обратными вызовами

      Функция обратного вызова — это функция, которая передается в качестве аргумента в другую функцию, а затем выполняется после завершения выполнения другой функции. Мы используем обратный вызов, чтобы убедиться, что код выполняется только после завершения асинхронной операции.

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

      Существует множество способов, позволяющих использовать обратные вызовы функции внутри другой функции. Обычно они имеют следующую структуру:

      function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {
          [ Action ]
      }
      

      Хотя синтаксис JavaScript или Node.js не требует, чтобы функция обратного вызова использовалась в качестве последнего аргумента внешней функции, это стандартная практика, которая упрощает определение обратных вызовов. Также, как правило, разработчики JavaScript используют анонимную функцию в качестве обратного вызова. Анонимные функции — это функции, которые не имеют имени. Обычно код читается гораздо лучше, когда функция определяется в конце списка аргументов.

      Чтобы продемонстрировать использование обратного вызова, давайте создадим модуль Node.js, который пишет список фильмов Studio Ghibli в файл. Во-первых, создайте папку, которая будет хранить файл JavaScript и вывод:

      Затем откройте эту папку:

      Начнем с создания запроса HTTP для Studio Ghibli API, где наша функция обратного вызова будет хранить результаты. Для этого мы установим библиотеку, которая позволяет нам получить доступ к данным HTTP-ответа в обратном вызове.

      В терминале инициализируйте npm, чтобы мы могли получить ссылку на наши пакеты позднее:

      Затем установите библиотеку request:

      Теперь откройте новый файл с именем callbackMovies.js в текстовом редакторе, например nano:

      В текстовом редакторе добавьте следующий код. Начнем с отправки запроса HTTP с помощью модуля request:

      callbackMovies.js

      const request = require('request');
      
      request('https://ghibliapi.herokuapp.com/films');
      

      В первой строке мы загрузим модуль request, который был установлен с помощью npm. Модуль возвращает функцию, которая может выполнять запросы HTTP; затем мы сохраним эту функцию в константе request.

      Затем мы создадим запрос HTTP с помощью функции request(). Теперь мы распечатаем данные из запроса HTTP в консоль, добавив выделенные изменения:

      callbackMovies.js

      const request = require('request');
      
      request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
          if (error) {
              console.error(`Could not send request to API: ${error.message}`);
              return;
          }
      
          if (response.statusCode != 200) {
              console.error(`Expected status code 200 but received ${response.statusCode}.`);
              return;
          }
      
          console.log('Processing our list of movies');
          movies = JSON.parse(body);
          movies.forEach(movie => {
              console.log(`${movie['title']}, ${movie['release_date']}`);
          });
      });
      

      При использовании функции request() мы передадим в нее два параметра:

      • URL-адрес сайта, который мы запрашиваем
      • Функция обратного вызова, которая обрабатывает любые ошибки или успешные ответы на выполненные запросы

      Наша функция обратного вызова имеет три аргумента: error, response и body. Когда запрос HTTP выполняется, аргументам автоматически присваиваются значения в зависимости от вывода. Если не удалось выполнить запрос, тогда error будет содержать объект, а response и body будут иметь значение null. Если запрос будет выполнен успешно, HTTP-ответ сохраняется в качестве значения аргумента response. Если наш HTTP-ответ возвращает данные (в данном примере мы получим JSON), данные сохранятся в качестве значения аргумента body.

      Наша функция обратного вызова сначала проверяет, была ли получена ошибка. Рекомендуется выполнять проверку на ошибки в обратном вызове в первую очередь, чтобы исполнение обратного вызова не продолжалось с неполными данными. В данном случае мы регистрируем ошибку и исполнение функции. Затем мы проверяем код состояния ответа. Наш сервер может не всегда быть доступным, а API могут менять, в результате чего когда-то рабочие запросы становятся неверными. Проверив, что код статуса равен 200, что означает, что запрос выполнен успешно, мы можем убедиться, что наш ответ будет выглядеть так, как мы ожидаем.

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

      После сохранения и закрытия файла запустите этот скрипт с помощью следующей команды:

      Результат будет выглядеть следующим образом:

      Output

      Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014

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

      Обновите файл callbackMovies.js в текстовом редакторе, чтобы включить следующий выделенный код, который создает файл CSV с данными нашего фильма:

      callbackMovies.js

      const request = require('request');
      const fs = require('fs');
      
      request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
          if (error) {
              console.error(`Could not send request to API: ${error.message}`);
              return;
          }
      
          if (response.statusCode != 200) {
              console.error(`Expected status code 200 but received ${response.statusCode}.`);
              return;
          }
      
          console.log('Processing our list of movies');
          movies = JSON.parse(body);
          let movieList = '';
          movies.forEach(movie => {
              movieList += `${movie['title']}, ${movie['release_date']}n`;
          });
      
          fs.writeFile('callbackMovies.csv', movieList, (error) => {
              if (error) {
                  console.error(`Could not save the Ghibli movies to a file: ${error}`);
                  return;
              }
      
              console.log('Saved our list of movies to callbackMovies.csv');;
          });
      });
      

      Если внимательно изучить внесенные изменения, можно увидеть, что мы импортируем модуль fs. Этот модуль является стандартным модулем для всех установок Node.js и содержит метод writeFile(), который осуществляет асинхронную запись в файл.

      Вместо того, чтобы выводить данные в консоль, мы добавим их в строковую переменную movieList. Затем мы воспользуемся writeFile() для сохранения содержимого movieList в новый файл callbackMovies.csv. В заключение мы передадим обратный запрос в функцию writeFile(), которая имеет только один аргумент: error. Это позволит нам обрабатывать случаи, когда выполнить запись в файл не удается, например, когда пользователь, с помощью которого мы выполняем запуск процесса node, не имеет необходимых разрешений.

      Сохраните файл и еще раз запустите нашу программу Node.js с помощью следующей команды:

      В папке ghibliMovies вы увидите файл callbackMovies.csv, который содержит следующее:

      callbackMovies.csv

      Castle in the Sky, 1986
      Grave of the Fireflies, 1988
      My Neighbor Totoro, 1988
      Kiki's Delivery Service, 1989
      Only Yesterday, 1991
      Porco Rosso, 1992
      Pom Poko, 1994
      Whisper of the Heart, 1995
      Princess Mononoke, 1997
      My Neighbors the Yamadas, 1999
      Spirited Away, 2001
      The Cat Returns, 2002
      Howl's Moving Castle, 2004
      Tales from Earthsea, 2006
      Ponyo, 2008
      Arrietty, 2010
      From Up on Poppy Hill, 2011
      The Wind Rises, 2013
      The Tale of the Princess Kaguya, 2013
      When Marnie Was There, 2014
      

      Необходимо отметить, что мы осуществляем запись в файл CSV в обратном вызове запроса HTTP. Так как код находится в функции обратного вызова, он будет осуществлять запись в файл только после выполнения запроса HTTP. Если мы хотим подключиться к базе данных после создания нашего файла CSV, нужно создать другую асинхронную функцию, которая будет вызываться в обратном вызове writeFile(). Чем более асинхронный код нам нужен, тем больше функций обратного вызова нам нужно добавить.

      Давайте представим, что мы хотим выполнять пять асинхронных операций, каждая из которых может выполняться только после завершения предыдущей. Если бы нам нужно было написать код с таким функционалом, у нас получилось бы примерно следующее:

      doSomething1(() => {
          doSomething2(() => {
              doSomething3(() => {
                  doSomething4(() => {
                      doSomething5(() => {
                          // final action
                      });
                  });
              });
          });
      });
      

      Когда вложенные обратные вызовы содержат много строк кода, они становятся значительно более сложными для понимания и чтения. По мере увеличения размера и сложности вашего JavaScript-проекта, данный эффект становится все более заметным до тех пор, пока в конце концов станет невозможно управлять этой конструкцией. По этой причине разработчики больше не используют обратные вызовы для обработки асинхронных операций. Чтобы улучшить синтаксис нашего асинхронного кода, мы можем использовать обещания.

      Использование обещаний и ответственное асинхронное программирование

      Обещание — это объект JavaScript, который будет возвращать значение в какой-то момент в будущем. Асинхронные функции могут возвращать объекты обещаний вместо конкретных значений. Если мы получим значение в будущем, то скажем, что обещание было выполнено. Если в будущем мы получим ошибку, то можем сказать, что обещание было отклонено. Во всех остальных случаях обещание обрабатывается в состоянии ожидания.

      Обещания обычно имеют следующую форму:

      promiseFunction()
          .then([ Callback Function for Fulfilled Promise ])
          .catch([ Callback Function for Rejected Promise ])
      

      Как показано в этом шаблоне, обещания также используют функции обратного вызова. У нас есть функция обратного вызова для метода then(), который выполняется при выполнении обещания. Также у нас есть функция обратного вызова для метода catch() для обработки любых ошибок, возникающих при исполнении обещания.

      Давайте получим практический опыт работы с обещаниями, переписав нашу программу Studio Ghibli, на этот раз с использованием обещаний.

      Axios — это HTTP-клиент JavaScript, работающий с обещаниями, так что давайте попробуем его установить:

      Теперь в любом текстовом редакторе на ваш выбор создайте новый файл promiseMovies.js:

      Наша программа выполняет запрос HTTP с помощью axios и затем использует специальную версию fs на базе обещаний, чтобы сохранить новый файл CSV.

      Введите этот код в файл promiseMovies.js, чтобы загрузить Axios и отправить запрос HTTP в API фильмов:

      promiseMovies.js

      const axios = require('axios');
      
      axios.get('https://ghibliapi.herokuapp.com/films');
      

      В первой строке мы загрузим модуль axios, сохранив возвращаемую функцию в константе с именем axios. Затем мы используем метод axios.get() для отправки запроса HTTP для API.

      Метод axios.get() возвращает обещание. Давайте используем это обещание, чтобы вывести список фильмов Ghibli в консоль:

      promiseMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      
      axios.get('https://ghibliapi.herokuapp.com/films')
          .then((response) => {
              console.log('Successfully retrieved our list of movies');
              response.data.forEach(movie => {
                  console.log(`${movie['title']}, ${movie['release_date']}`);
              });
          })
      

      Давайте разберем, что здесь происходит. После выполнения HTTP-запроса GET с помощью axios.get() мы используем функцию then(), которая выполняется только при выполнении обещания. В данном случае мы выводим фильмы на экран, как мы делали в примере с обратным вызовом.

      Чтобы улучшить эту программу, добавьте выделенный код для записи данных HTTP в файл:

      promiseMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      
      axios.get('https://ghibliapi.herokuapp.com/films')
          .then((response) => {
              console.log('Successfully retrieved our list of movies');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
      
              return fs.writeFile('promiseMovies.csv', movieList);
          })
          .then(() => {
              console.log('Saved our list of movies to promiseMovies.csv');
          })
      

      Еще раз дополнительно импортируем модуль fs. Обратите внимание, как после импорта fs мы получили .promises. Node.js включает работающую на базе обещаний версию библиотеки fs на базе обратного вызова, так что при использовании в старых проектах обратная совместимость не пострадает.

      Первая функция then(), которая обрабатывает запрос HTTP, теперь вызывает fs.writeFile() вместо вывода в консоль. Поскольку мы импортировали версию fs на базе обещаний, наша функция writeFile() возвращает другое обещание. Фактически мы добавили другую функцию then() для случаев, когда обещание writeFile() выполняется.

      Обещание может возвращать новое обещание, что позволяет нам выполнять обещания одно за другим. Это, в свою очередь, дает нам возможность реализовать несколько асинхронных операций. Это называется цепочкой обещаний и является аналогом вложенных функций обратного вызова. Вторая функция then() вызывается только после успешной записи в файл.

      Примечание. В данном примере мы не проверяли код состояния HTTP, как мы делали в примере обратного вызова. По умолчанию axios не выполняет свое обещание при получении кода состояния, указывающего на ошибку. Т. е. нам больше не нужно выполнять эту проверку.

      Чтобы выполнить эту программу, свяжите обещание с функцией catch(), как показано в выделенном фрагменте ниже:

      promiseMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      
      axios.get('https://ghibliapi.herokuapp.com/films')
          .then((response) => {
              console.log('Successfully retrieved our list of movies');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
      
              return fs.writeFile('promiseMovies.csv', movieList);
          })
          .then(() => {
              console.log('Saved our list of movies to promiseMovies.csv');
          })
          .catch((error) => {
              console.error(`Could not save the Ghibli movies to a file: ${error}`);
          });
      

      Если какое-либо обещание в цепи обещаний не выполняется, JavaScript автоматически переходит в функцию catch(), если она определена. Именно поэтому у нас есть только один вызов catch(), хотя мы используем две асинхронные операции.

      Давайте подтвердим, что наша программа генерирует аналогичный результат, выполнив запуск:

      В папке ghibliMovies вы увидите файл promiseMovies.csv содержащий следующее:

      promiseMovies.csv

      Castle in the Sky, 1986
      Grave of the Fireflies, 1988
      My Neighbor Totoro, 1988
      Kiki's Delivery Service, 1989
      Only Yesterday, 1991
      Porco Rosso, 1992
      Pom Poko, 1994
      Whisper of the Heart, 1995
      Princess Mononoke, 1997
      My Neighbors the Yamadas, 1999
      Spirited Away, 2001
      The Cat Returns, 2002
      Howl's Moving Castle, 2004
      Tales from Earthsea, 2006
      Ponyo, 2008
      Arrietty, 2010
      From Up on Poppy Hill, 2011
      The Wind Rises, 2013
      The Tale of the Princess Kaguya, 2013
      When Marnie Was There, 2014
      

      С обещаниями мы можем писать более сжатый и понятный код, по сравнению с обратными вызовами. Цепь обещаний с обратными вызовами представляет собой более понятный вариант, чем вложенные обратные вызовы. Однако при создании большого количества асинхронных вызовов наша цепочка обещаний становится более сложной и трудно поддерживаемой.

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

      Использование async и await в JavaScript

      Ключевые слова async/await предоставляют возможность использования альтернативного синтаксиса при работе с обещаниями. Вместо получения результата обещания в методе then() результат возвращается в качестве значения, как в любой другой функции. Мы определяем функцию с ключевым словом async, чтобы указать JavaScript, что это асинхронная функция, возвращающая обещание. Мы используем ключевое слово await, чтобы указать JavaScript возвращать результаты обещания вместо возвращения самого обещания при его выполнении.

      Использование async/await выглядит следующим образом:

      async function() {
          await [Asynchronous Action]
      }
      

      Давайте посмотрим, как использование async/await может улучшить нашу программу Studio Ghibli. Воспользуйтесь текстовым редактором для создания и открытия нового файла asyncAwaitMovies.js:

      В новом файле JavaScript мы сначала импортируем те же самые модули, которые мы использовали при работе с обещаниями:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      

      Импорт выполняется аналогично импорту в promiseMovies.js, поскольку async/await используют обещания.

      Теперь мы используем слово async для создания функции с нашим асинхронным кодом:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {}
      

      Мы создадим новую функцию с именем saveMovies(), но добавим async в начале ее определения. Это важно, поскольку мы можем использовать ключевое слово await только в асинхронной функции.

      Используйте ключевое слово await для создания запроса HTTP, получающего список фильмов из Ghibli API:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          let response = await axios.get('https://ghibliapi.herokuapp.com/films');
          let movieList = '';
          response.data.forEach(movie => {
              movieList += `${movie['title']}, ${movie['release_date']}n`;
          });
      }
      

      В функции saveMovies() мы создаем запрос HTTP с помощью axios.get(), как и прежде. На этот раз мы не соединяем ее с функцией then(). Вместо этого мы добавим await перед ее вызовом. Когда JavaScript видит await, выполняется остальной код функции только после того,как axios.get() завершит выполнение и задаст переменную response. Остальной код сохраняет данные фильмов, чтобы мы могли записывать их в файл.

      Давайте запишем данные в файл:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          let response = await axios.get('https://ghibliapi.herokuapp.com/films');
          let movieList = '';
          response.data.forEach(movie => {
              movieList += `${movie['title']}, ${movie['release_date']}n`;
          });
          await fs.writeFile('asyncAwaitMovies.csv', movieList);
      }
      

      Мы также используем ключевое слово await при записи в файл с помощью fs.writeFile().

      Для выполнения этой функции нам нужно поймать ошибки, которые могут генерировать наши обещания. Давайте выполним это, обернув наш код в блок try/catch:

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          try {
              let response = await axios.get('https://ghibliapi.herokuapp.com/films');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
              await fs.writeFile('asyncAwaitMovies.csv', movieList);
          } catch (error) {
              console.error(`Could not save the Ghibli movies to a file: ${error}`);
          }
      }
      
      

      Поскольку обещания могут не выполняться, мы поместим наш асинхронный код внутри блока try/catch. Он будет перехватывать любые ошибки, которые будут генерироваться при невозможности выполнения запроса HTTP или записи в файл.

      В заключение мы вызовем нашу асинхронную функцию saveMovies(), которая будет выполняться при запуске программы с помощью node.

      asyncAwaitMovies.js

      const axios = require('axios');
      const fs = require('fs').promises;
      
      async function saveMovies() {
          try {
              let response = await axios.get('https://ghibliapi.herokuapp.com/films');
              let movieList = '';
              response.data.forEach(movie => {
                  movieList += `${movie['title']}, ${movie['release_date']}n`;
              });
              await fs.writeFile('asyncAwaitMovies.csv', movieList);
          } catch (error) {
              console.error(`Could not save the Ghibli movies to a file: ${error}`);
          }
      }
      
      saveMovies();
      

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

      Проверьте эту версию нашей программы, запустив ее в терминале:

      В папке ghibliMovies будет создан новый файл asyncAwaitMovies.csv со следующим содержанием:

      asyncAwaitMovies.csv

      Castle in the Sky, 1986
      Grave of the Fireflies, 1988
      My Neighbor Totoro, 1988
      Kiki's Delivery Service, 1989
      Only Yesterday, 1991
      Porco Rosso, 1992
      Pom Poko, 1994
      Whisper of the Heart, 1995
      Princess Mononoke, 1997
      My Neighbors the Yamadas, 1999
      Spirited Away, 2001
      The Cat Returns, 2002
      Howl's Moving Castle, 2004
      Tales from Earthsea, 2006
      Ponyo, 2008
      Arrietty, 2010
      From Up on Poppy Hill, 2011
      The Wind Rises, 2013
      The Tale of the Princess Kaguya, 2013
      When Marnie Was There, 2014
      

      Теперь вы используете функции JavaScript async/await для управления асинхронным кодом.

      Заключение

      В этом обучающем руководстве вы узнали, как JavaScript обрабатывает функции и управляет асинхронными операциями с помощью цикла событий. Затем мы написали программы, которые создают файл CSV после отправки запроса HTTP для получения данных о фильмах с использованием различных асинхронных техник программирования. Сначала мы использовали устаревший подход с обратными вызовами. Затем мы использовали обещания и, наконец, поработали с async/await, чтобы сделать синтаксис обещаний более сжатым.

      Обладая пониманием асинхронного кода в Node.js, мы можем разрабатывать программы, которые могут использовать преимущества асинхронного программирования, например программы, опирающиеся на вызовы API. Посмотрите на этот список публичных API. Для их использования вам нужно выполнять асинхронные запросы HTTP, как мы делали в этом обучающем руководстве. Для дальнейшего изучения попробуйте создать приложение, использующее эти API, чтобы попрактиковаться с описанными здесь техниками.



      Source link

      Написание комментариев в Go


      Введение

      Комментарии — это строки компьютерных программ, которые игнорируются компиляторами и интерпретаторами. Добавление комментариев в программы делает код более удобным для чтения людьми, поскольку комментарии обычно содержат информацию или разъяснение того, что делает каждая часть программы.

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

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

      Синтаксис комментариев

      Комментарии в Go начинаются с набора косых черт (//) и продолжаются до конца строчки. После набора косых черт принято ставить пробел.

      Обычно комментарии выглядят примерно так:

      // This is a comment
      

      Комментарии не выполняются, поэтому при выполнении программы с ними ничего не происходит. Комментарии в исходном коде предназначены для чтения людьми, а не для выполнения компьютерами.

      В программе Hello, World! комментарий может выглядеть так:

      hello.go

      package main
      
      import (
          "fmt"
      )
      
      func main() {
          // Print “Hello, World!” to console
          fmt.Println("Hello, World!")
      }
      
      

      В цикле for с итерацией среза комментарии могут выглядеть так:

      sharks.go

      package main
      
      import (
          "fmt"
      )
      
      func main() {
          // Define sharks variable as a slice of strings
          sharks := []string{"hammerhead", "great white", "dogfish", "frilled", "bullhead", "requiem"}
      
          // For loop that iterates over sharks list and prints each string item
          for _, shark := range sharks {
              fmt.Println(shark)
          }
      }
      

      Комментарии следует создавать с тем же отступом, что и в комментируемом коде. Так для определения функции без отступа следует указывать комментарий без отступа, а на каждом следующем уровне отступа комментарии должны соответствовать комментируемому коду

      Например, так комментируется функция main в соответствии с уровнями отступа в коде:

      color.go

      package main
      
      import "fmt"
      
      const favColor string = "blue"
      
      func main() {
          var guess string
          // Create an input loop
          for {
              // Ask the user to guess my favorite color
              fmt.Println("Guess my favorite color:")
              // Try to read a line of input from the user. Print out the error 0
              if _, err := fmt.Scanln(&guess); err != nil {
                  fmt.Printf("%sn", err)
                  return
              }
              // Did they guess the correct color?
              if favColor == guess {
                  // They guessed it!
                  fmt.Printf("%q is my favorite color!n", favColor)
                  return
              }
              // Wrong! Have them guess again.
              fmt.Printf("Sorry, %q is not my favorite color. Guess again.n", guess)
          }
      }
      

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

      При комментрировании кода нужно отвечать на вопрос почему используется такой код, а не что это или как это делается. Если код не особенно сложный, вы можете понять, что и как делается, просто посмотрев на него, и именно поэтому комментарии в основном отвечают на вопрос почему.

      Блоки комментариев

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

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

      // First line of a block comment
      // Second line of a block comment
      

      Второй вариант заключается в использовании открывающих тегов (/*) и закрывающих тегов (*/). Для документирования кода принято всегда использовать синтаксис //. Синтаксис /* ... */ используется только для отладки, о чем мы поговорим позднее в этой статье.

      /*
      Everything here
      will be considered
      a block comment
      */
      

      В этом примере блок комментария определяет, что происходит в функции MustGet():

      function.go

      // MustGet will retrieve a url and return the body of the page.
      // If Get encounters any errors, it will panic.
      func MustGet(url string) string {
          resp, err := http.Get(url)
          if err != nil {
              panic(err)
          }
      
          // don't forget to close the body
          defer resp.Body.Close()
          var body []byte
          if body, err = ioutil.ReadAll(resp.Body); err != nil {
              panic(err)
          }
          return string(body)
      }
      

      Обычно блоки комментариев размещаются в начале экспортируемых функций Go. Эти комментарии также используются для создания документации по коду. Блоки комментариев также полезны, когда операции не очень понятны с первого взгляда и требуют более подробного разъяснения. За исключением случаев документирования функций, следует избегать чрезмерного комментирования кода и ожидать, что другие программисты поймут код на Go, если только вы не пишете для конкретной аудитории.

      Комментарии внутри строк

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

      Обычно комментарии внутри строк выглядят следующим образом:

      [code]  // Inline comment about the code
      

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

      Например, если вы не используете большое количество математических операций в программах Go, вы или ваши коллеги можете не знать, что следующее выражение создает комплексное число, и поэтому лучше оставить соответствующий комментарий:

      z := x % 2  // Get the modulus of x
      

      Также комментарии в строках можно использовать для разъяснения причин каких-то действий или предоставления дополнительной информации, как в следующем примере:

      x := 8  // Initialize x with an arbitrary number
      

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

      Исключение частей кода в виде комментариев для целей тестирования

      Помимо использования комментариев для документирования кода, вы можете использовать открывающие теги (/*) и закрывающие теги (*/) для создания блока комментариев. Это позволяет отделить определенную часть кода, которую вы не хотите выполнять при тестировании или отладке создаваемой программы. Когда возникают ошибки после написания новых строчек кода, вы можете закомментировать некоторые из них, чтобы выявить источник проблемы.

      Теги /* и */ также позволяют проверять различные альтернативные варианты при определении способов настройки кода. Также блоки комментариев можно использовать, чтобы закомментировать нежелательный код во время работы над другими частями кода.

      multiply.go

      // Function to add two numbers
      func addTwoNumbers(x, y int) int {
          sum := x + y
          return sum
      }
      
      // Function to multiply two numbers
      func multiplyTwoNumbers(x, y int) int {
          product := x * y
          return product
      }
      
      func main() {
          /*
              In this example, we're commenting out the addTwoNumbers
              function because it is failing, therefore preventing it from executing.
              Only the multiplyTwoNumbers function will run
      
              a := addTwoNumbers(3, 5)
              fmt.Println(a)
      
          */
      
          m := multiplyTwoNumbers(5, 9)
          fmt.Println(m)
      }
      

      Примечание: закомментировать части кода можно только для тестирования. Не оставляйте в окончательной версии программы закомментированные фрагменты кода.

      Исключение кода с помощью тегов комментариев /* и */ позволяет пробовать разные методы программирования или находить источники ошибок посредством последовательного исключения и запуска частей программы.

      Заключение

      Использование комментариев в программах Go помогает делать программы более удобочитаемыми для всех работающих над проектом, включая вас. Добавление подходящих, актуальных и полезных комментариев упрощает совместную работу над программными проектами и делает ценность вашего кода более очевидной.

      Правильное комментирование кода в Go также позволяет использовать инструмент Godoc. Godoc — это инструмент, извлекающий комментарии из кода и генерирующий документацию для вашей программы Go.



      Source link

      Написание условных выражений в Go


      Введение

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

      Когда мы полностью исполняем каждое выражение программы, мы не просим, чтобы программа оценивала определенные условия. С условными выражениями программы могут определять выполнение определенных условий и получать соответствующие указания.

      Рассмотрим несколько примеров, когда стоит использовать условные выражения:

      • Если ученица получает за тест 65% или больше, она сдает его; если нет — не сдает.
      • Если у человека есть деньги на счете, рассчитывается процент, а если нет — начисляется штраф.
      • Если покупатель приобретает 10 апельсинов или более, он получает скидку 5%; если же он покупает меньше, скидка не предоставляется.

      Оценка условий и назначение кода для запуска в зависимости от выполнения условий — это основа использования условных выражений.

      В этом обучающем руководстве мы научимся писать условные выражения на языке программирования Go.

      Оператор If

      Начнем с выражений if, которые оценивают истинность утверждения и запускают код, только если оно истинно.

      Откройте в текстовом редакторе файл и введите следующий код:

      grade.go

      package main
      
      import "fmt"
      
      func main() {
          grade := 70
      
          if grade >= 65 {
              fmt.Println("Passing grade")
          }
      }
      

      В этом коде мы используем переменную grade и присваиваем ей значение 70. Затем мы используем оператор if для проверки того, что значения переменной grade больше или равно ( >= ) числу 65. Если это условие выполняется, мы указываем программе распечатать строку Passing grade.

      Сохраните программу с именем grade.go и запустите ее в локальной среде программирования из окна терминала с помощью команды go run grade.go.

      В данном случае оценка 70 соответствует условию (больше или равно 65), и после запуска программы вы получите следующий результат:

      Output

      Passing grade

      Теперь изменим результат программы, сменив значение переменной grade на 60:

      grade.go

      package main
      
      import "fmt"
      
      func main() {
          grade := 60
      
          if grade >= 65 {
              fmt.Println("Passing grade")
          }
      }
      

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

      Рассмотрим еще один пример, где мы будем рассчитывать, является ли баланс счета отрицательным. Создайте файл account.go и напишите следующую программу:

      account.go

      package main
      
      import "fmt"
      
      func main() {
          balance := -5
      
          if balance < 0 {
              fmt.Println("Balance is below 0, add funds now or you will be charged a penalty.")
          }
      }
      

      При запуске этой программы с помощью комады go run account.go мы получим следующий результат:

      Output

      Balance is below 0, add funds now or you will be charged a penalty.

      В программе мы инициализировали переменную balance со значением -5, и это значение меньше 0. Поскольку баланс соответствует условию выражения if (balance < 0), при сохранении и запуске этого кода будет выведена строка. Если мы изменим баланс на 0 или положительное число, ничего выводиться не будет.

      Оператор Else

      Часто нам может понадобиться, чтобы программа что-то делала, даже когда оператор if равен false. В примере с оценкой мы хотим увидеть, сдан ли зачет.

      Для этого мы добавим к условию выражение else следующим образом:

      grade.go

      package main
      
      import "fmt"
      
      func main() {
          grade := 60
      
          if grade >= 65 {
              fmt.Println("Passing grade")
          } else {
              fmt.Println("Failing grade")
          }
      }
      

      Поскольку переменная grade имеет значение 60, выражение if оценивается как ложное, и программа не распечатывает текст Passing grade. Однако следующее выражение else предписывает программе выполнить другое действие.

      Когда мы сохраним и запустим эту программу, мы увидим следующее:

      Output

      Failing grade

      Если мы перепишем программу и присвоим переменной значение 65 или выше, вместо этого будет выводиться строка Passing grade.

      Чтобы добавить выражение else в пример с банковским счетом, мы перепишем код следующим образом:

      account.go

      package main
      
      import "fmt"
      
      func main() {
          balance := 522
      
          if balance < 0 {
              fmt.Println("Balance is below 0, add funds now or you will be charged a penalty.")
          } else {
              fmt.Println("Your balance is 0 or above.")
          }
      }
      

      Output

      Your balance is 0 or above.

      Здесь мы изменили значение переменной balance на положительное число, чтобы выполнить вывод в операторе else. Чтобы выполнить вывод первого оператора if, мы можем заменить значение отрицательным числом.

      Сочетая выражение if с выражением else, вы составляете условное выражение из двух частей, которое требует от компьютера выполнять определенный код если условие if выполнено, и если оно не выполнено.

      Операторы Else if

      Мы использовали для условных выражений опцию Boolean, с которой каждое выражение if оценивается или как истинное, или как ложное. Во многих случаях нам может потребоваться программа, которая будет оценивать более двух возможных результатов. Для этой цели мы используем выражение else if, которое имеет в Go синтаксис else if. Выражение else if выглядит похожим на выражение if и оценивает другое условие.

      В программе банковского счета нам может потребоваться три варианта вывода для трех разных случаев:

      • Баланс меньше 0
      • Баланс равен 0
      • Баланс больше 0

      Выражение else if помещается между выражением if и выражением else следующим образом:

      account.go

      package main
      
      import "fmt"
      
      func main() {
          balance := 522
      
          if balance < 0 {
              fmt.Println("Balance is below 0, add funds now or you will be charged a penalty.")
          } else if balance == 0 {
              fmt.Println("Balance is equal to 0, add funds soon.")
          } else {
              fmt.Println("Your balance is 0 or above.")
          }
      }
      

      Теперь при запуске программы возможны три результата:

      • Если переменная balance равняется 0, будет выведен результат выражения else if (Balance is equal to 0, add funds soon.)
      • Если переменная balance равняется положительному числу, будет выведен результат выражения else (Your balance is 0 or above.).
      • Если переменная balance равняется отрицательному числу, будет выведен результат выражения if (Balance is below 0, add funds now or you will be charged a penalty).

      Что делать, если нам требуется более трех вероятностей? В этом случае мы можем вставить в код несколько выражений else if.

      В программе grade.go мы перепишем код так, чтобы числовым оценкам соответствовали разные буквенные оценки:

      • 90 или выше соответствует оценке A
      • 80-89 соответствует оценке B
      • 70-79 соответствует оценке C
      • 65-69 соответствует оценке D
      • 64 или менее соответствует оценке F

      Для этого кода нам потребуется одно выражение if, три выражения else if и выражение else для всех случаев несоответствия.

      Перепишем код из предыдущего примера так, чтобы в строках распечатывались буквенные оценки. Мы можем сохранить выражение else без изменений.

      grade.go

      package main
      
      import "fmt"
      
      func main() {
          grade := 60
      
          if grade >= 90 {
              fmt.Println("A grade")
          } else if grade >= 80 {
              fmt.Println("B grade")
          } else if grade >= 70 {
              fmt.Println("C grade")
          } else if grade >= 65 {
              fmt.Println("D grade")
          } else {
              fmt.Println("Failing grade")
          }
      }
      

      Поскольку выражения else if будут проверяться по порядку, система выражений может быть очень простой. Данная программа выполняет следующие шаги:

      1. Если оценка больше 90, программа выводит текст A grade, если оценка меньше 90, программа переходит к следующему выражению…

      2. Если оценка больше или равна 80, программа выводит текст B grade, если оценка равняется 79 или менее, программа переходит к следующему выражению…

      3. Если оценка больше или равна 70, программа выводит текст С grade, если оценка равняется 69 или менее, программа переходит к следующему выражению…

      4. Если оценка больше или равна 65, программа выводит текст D grade, если оценка равняется 64 или менее, программа переходит к следующему выражению…

      5. Программа выводит текст Failing grade, поскольку ни одно из предыдущих условий не выполнено.

      Вложенные операторы If

      Вы познакомились с выражениями if, else if и else и теперь можете переходить к вложенным условным выражениям. Вложенные выражения if используются в ситуациях, когда нам нужно проверить дополнительное условие, если первое условие определяется как истинное. Для этого мы можем использовать выражение if-else внутри другого выражения if-else. Рассмотрим синтаксис вложенного выражения if:

      if statement1 { // outer if statement
          fmt.Println("true")
      
          if nested_statement { // nested if statement
              fmt.Println("yes")
          } else { // nested else statement
              fmt.Println("no")
          }
      
      } else { // outer else statement
          fmt.Println("false")
      }
      

      Этот код имеет несколько вариантов выводимых результатов:

      • Если выражение statement1 оценивается как истинное, программа проверяет истинность выражения nested_statement. Если оба выражения истинные, результат будет выглядеть следующим образом:

      Output

      true yes
      • Если выражение statement1 истинное, а выражение nested_statement — ложное, результат будет выглядеть следующим образом:

      Output

      true no
      • Если выражение statement1 ложное, вложенное выражение if-else не проверяется, и выполняется только выражение else. Результат будет выглядеть следующим образом:

      Output

      false

      Мы можем использовать в коде несколько вложенных выражений if:

      if statement1 { // outer if
          fmt.Println("hello world")
      
          if nested_statement1 { // first nested if
              fmt.Println("yes")
      
          } else if nested_statement2 { // first nested else if
              fmt.Println("maybe")
      
          } else { // first nested else
              fmt.Println("no")
          }
      
      } else if statement2 { // outer else if
          fmt.Println("hello galaxy")
      
          if nested_statement3 { // second nested if
              fmt.Println("yes")
          } else if nested_statement4 { // second nested else if
              fmt.Println("maybe")
          } else { // second nested else
              fmt.Println("no")
          }
      
      } else { // outer else
          statement("hello universe")
      }
      

      В этом коде вложенное выражение if используется внутри каждого выражения if в дополнение к выражению else if. Это дает больше вариантов для каждого условия.

      Рассмотрим пример вложенных выражений if в нашей программе grade.go. Вначале мы проверяем, соответствует ли оценка прохождению теста (больше или равняется 65%), а затем определяем буквенную оценку, которая соответствует числовой. Если оценка не соответствует прохождению, нам не нужно проверять буквенные значения, и программа просто сообщает, что тест не пройден. Наш измененный код с вложенным выражением if будет выглядеть следующим образом:

      grade.go

      
      package main
      
      import "fmt"
      
      func main() {
          grade := 92
          if grade >= 65 {
              fmt.Print("Passing grade of: ")
      
              if grade >= 90 {
                  fmt.Println("A")
      
              } else if grade >= 80 {
                  fmt.Println("B")
      
              } else if grade >= 70 {
                  fmt.Println("C")
      
              } else if grade >= 65 {
                  fmt.Println("D")
              }
      
          } else {
              fmt.Println("Failing grade")
          }
      }
      

      Если мы запустим код с переменной grade с целочисленным значением 92, первое условие выполняется, и программа выводит текст Passing grade of: . Затем программа видит, что оценка больше или равна 90, и поскольку это условие также выполнено, она выводит A.

      Если мы запустим код с переменной grade со значением 60, первое условие не будет выполняться, и программа пропустит вложенные выражения if и перейдет к выражению else, в результате чего будет выведен текст Failing grade.

      Мы можем добавить еще больше вариантов и использовать второй уровень вложенных выражений if. Например, мы можем захотеть использовать отдельные оценки A+, A и A-. Для этого вначале проверяем, что тест пройден, затем проверяем, что оценка равняется 90 или более, а затем проверим, что оценка больше 96 и соответствует значению A+:

      grade.go

      ...
      if grade >= 65 {
          fmt.Print("Passing grade of: ")
      
          if grade >= 90 {
              if grade > 96 {
                  fmt.Println("A+")
      
              } else if grade > 93 && grade <= 96 {
                  fmt.Println("A")
      
              } else {
                  fmt.Println("A-")
              }
      ...
      

      В этом коде, если для переменной grade задано значение 96, программа выполняет следующие действия:

      1. Проверяет, что оценка больше или равна 65 (истина)
      2. Выводит текст Passing grade of:
      3. Проверяет, что оценка больше или равна 90 (истина)
      4. Проверяет, что оценка больше 96 (ложь)
      5. Проверяет, что оценка больше 93 и меньше или равна 96 (истина)
      6. Выводит A
      7. Покидает вложенные условные выражения и продолжает выполнение остальной части кода

      Таким образом, для оценки 96 результат выполнения программы будет выглядеть так:

      Output

      Passing grade of: A

      Вложенные выражения if позволяют добавлять в код несколько определенных уровней условий.

      Заключение

      Использование условных выражений, в том числе выражения if, вы можете лучше контролировать выполняемый программой код. Условные выражения предписывают программе оценить выполнение определенных условий. Если условие выполнено, запускается определенный код, а если оно не выполнено — программа переходит к другому коду.

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



      Source link